Posso usar a API do Discourse para autenticar usuários em outro aplicativo?

Tenho um fórum Discourse funcionando, onde os usuários podem criar um login local (nome de usuário + senha).

Gostaria de reutilizar o login e a senha deles em outro aplicativo. Em outras palavras: os usuários inserem o nome de usuário e a senha no outro aplicativo, e o aplicativo deve ser capaz de verificar se este é um login válido para o fórum.

Tenho lido a documentação da API do Discourse. Muitas coisas são possíveis, incluindo definir nome de usuário e senha para um determinado usuário, mas não encontrei um endpoint de API para validar um nome de usuário e senha existentes em relação à lista de usuários do fórum.

Presumo que tal endpoint de API deva existir, pois o fórum deve ser capaz de fazer isso para fazer login de um usuário através da interface web.

Qual é o endpoint da API para verificar nome de usuário e senha para fazer login no fórum?

3 curtidas

Mais diretamente, você pode usar o DiscourseConnect como um mecanismo para validar usuários ou, fora de sua aplicação, usar o discourse-auth-proxy.

Estes são métodos sugeridos para autenticar usuários em vez de lidar diretamente com credenciais de login. Isso também significa que você não precisa tentar lidar com detalhes de 2FA.

5 curtidas

Na verdade, minha "outra aplicação" é um aplicativo desktop, não um aplicativo web. Acho que o discourse-auth-proxy não funcionará nesse caso.

Na página do DiscourseConnect, uma das primeiras afirmações é esta:

Muitos sites que desejam se integrar a um site Discourse querem manter todo o registro de usuários em um site separado. Nessa configuração, todas as operações de login devem ser terceirizadas para esse site diferente.

Isso é exatamente o oposto do que eu quero fazer: quero terceirizar todas as operações de login para o Discourse. Existe alguma maneira de usar o DiscourseConnect para fazer isso?

1 curtida

Sim, absolutamente.

A coisa complicada é que existe um segredo compartilhado entre o provedor (Discourse) e o consumidor (seu aplicativo). Se você distribuir seu aplicativo, os usuários terão acesso a todos os segredos nele.

Colocar um proxy de autenticação na frente de um serviço web mínimo personalizado que fornece um token assinado ao seu aplicativo pode funcionar bem.

Tenho certeza de que existem outras maneiras de fazer isso que não estou pensando.

Você está se referindo à chave de API? Parece possível criar uma chave de API “granular”, que tenha acesso apenas a pontos de extremidade específicos da API. Ainda não está claro para mim quais pontos de extremidade seriam necessários se eu usar essa abordagem. Você sabe?

Sim, um serviço web mínimo com um proxy de autenticação pode ser uma boa solução; terei que experimentar um pouco para descobrir.

Não exatamente - seria o valor discourse connect provider secrets para o aplicativo, que precisaria ser definido em conjunto com enable discourse connect provider.

Mais informações sobre isso são explicadas aqui: Use Discourse as an identity provider (SSO, DiscourseConnect)

1 curtida

Para um aplicativo de desktop, talvez este método usado neste exemplo do React Native possa ser útil para você:

https://github.com/pmusaraj/discourse-mobile-single-site-app/blob/main/js/Authenticate.js

1 curtida

Se entendi corretamente, este método significaria que o usuário faz login usando um navegador. Isso pode funcionar, embora eu esperasse encontrar um método onde nome de usuário e senha pudessem ser inseridos em nosso aplicativo desktop, sem abrir um navegador.

Entendo que a abordagem que tenho em mente não suportará TFA a menos que eu a implemente, e que não suportará logins via provedores de terceiros (Google, Facebook, Discord, …).

Não acredito que isso seja explicitamente suportado, mas eu criaria uma sessão da mesma forma que o usuário faria no navegador web:


curl 'https://try.discourse.org/session' \
  -H 'sec-ch-ua: "Chromium";v="113", "Not-A.Brand";v="24"' \
  -H 'Discourse-Present: true' \
  -H 'DNT: 1' \
  -H 'X-CSRF-Token: …' \
  -H 'sec-ch-ua-mobile: ?0' \
  -H 'User-Agent: …' \
  -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' \
  -H 'Accept: */*' \
  -H 'Referer: https://try.discourse.org/' \
  -H 'X-Requested-With: XMLHttpRequest' \
  -H 'sec-ch-ua-platform: "Linux"' \
  --data-raw 'login=demouser&password=demopassword&second_factor_method=1&timezone=America%2FToronto' \
  --compressed

e duplicaria essa lógica de login no aplicativo.

Pelo que entendo neste momento, parece que o método usado no exemplo do Reactive Native pode ser transposto para nossa aplicação desktop (que é em Python).

O ponto de acesso da API usado parece ser <site>/session, e ele recebe um nome de usuário, senha e um token csrf. O token csrf pode ser obtido em <site>/session/csrf.

Isso é muito próximo do que eu estava procurando. Acho que vou tentar isso, reportarei se funcionar para mim.

O ponto de acesso da API <site>/session está documentado em algum lugar?

1 curtida

A melhor maneira de obter o que você deseja em um aplicativo desktop é usando as Chaves de API de Usuário.

Você precisa de uma interface web, seja no aplicativo ou abrindo o navegador, mas se você fizer seu aplicativo ser um manipulador para o protocolo usado pelos aplicativos móveis, você pode facilmente obter o token dessa maneira e só precisar usar o navegador novamente se o token expirar ou eles usarem um dispositivo diferente.

Minha experiência pessoal com isso é que usar as Chaves de API de Usuário é uma opção muito mais segura e simples do que tentar usar os endpoints de sessão. :slight_smile:

2 curtidas

Aqui estão 20 linhas de código Python que fazem aproximadamente o mesmo que o código React Native referido por @renato (exceto que não há compatibilidade com o Discourse 2.5 - eu não preciso disso)

Funciona bem, assumindo que você está usando login básico baseado em nome de usuário e senha. Ainda vou investigar os métodos alternativos, usando o login SSO do Discourse conforme configurado na instância do Discourse.

import requests
import json

def discourse_authenticate(url, name, password):
    session = requests.Session()
    session.headers.update({'X-Requested-With': 'XMLHttpRequest'})
    r1 = session.get(url + '/session/csrf')
    csrf_token = json.loads(r1.text).get('csrf')
    r2 = session.post(url + '/session',
        data={
            'login': name,
            'password': password,
            'authenticity_token': csrf_token,
        },
    )
    if r2.status_code != 200:
        return None
    return json.loads(r2.text)

Tentei aplicar isso, mas não consigo fazer funcionar. Abaixo está um código Python (simplificado) que gera uma URL para .../session/sso_provider. Quando tento, recebo Login Error. Não faço ideia do que isso significa.

import secrets
import base64
import urllib.parse
import hmac
import hashlib

forum_url = 'https://forum.embeetle.com'
target_url = 'https://embeetle.com/#account'
sso_secret = b'JCLSVcqbAnEPXz2p2xBY'

nonce = secrets.token_urlsafe()
payload = f'nonce={nonce}&return_sso_url={target_url}'
payload_base64 = base64.b64encode(payload.encode('utf-8')).decode()
payload_for_url = urllib.parse.quote(payload_base64)

payload_for_url = 'bm9uY2U9YklKeEU1WWw2OFhjSkJydGlwSU15UTRZeVlMeWd6ZzQyUU9mOFo0SWF5QSZyZXR1cm5fc3NvX3VybD1odHRwczovL2VtYmVldGxlLmNvbS8jYWNjb3VudA%3D%3D'

signature = hmac.new(
    sso_secret, payload_for_url.encode('utf-8'), hashlib.sha256
).hexdigest()

print(f'{forum_url}/session/sso_provider?sso={payload_for_url}&sig={signature}')

Por exemplo, uma execução pode gerar a URL no comando curl abaixo:

johan@morla:~/sa\> curl 'https://forum.embeetle.com/session/sso_provider?sso=bm9uY2U9YklKeEU1WWw2OFhjSkJydGlwSU15UTRZeVlMeWd6ZzQyUU9mOFo0SWF5QSZyZXR1cm5fc3NvX3VybD1odHRwczovL2VtYmVldGxlLmNvbS8jYWNjb3VudA%3D%3D&sig=a392ebb81b93ba7411290fbd00240921ae053bbb82998830dda994c8a71853da'
Login Errorjohan@morla:~/sa\> 

Como administrador, ative o verbose discourse connect logging, tente, e então verifique /logs no seu fórum para ver erros mais detalhados, por exemplo https://forum.embeetle.com/logs

Você verá, por exemplo:

Você precisa assinar o payload, não o payload citado, por exemplo:

signature = hmac.new(
  sso_secret, payload_base64.encode('utf-8'), hashlib.sha256
).hexdigest()

E então funciona!

Aliás, você precisa revogar e alterar este segredo imediatamente porque qualquer pessoa com ele pode fazer login no seu aplicativo, como acabei de fazer durante o teste.

3 curtidas

Não se preocupe, a página para a qual estou redirecionando é uma página pública, apenas para testes.

De qualquer forma, alterei o segredo.

2 curtidas

Quais são suas 3 principais sugestões de código aberto para essa tarefa específica?

Estou usando Nginx, mas provavelmente isso poderia ser gerenciado com um desenvolvimento mais robusto como Keycloak?

Por auth-proxy, quero dizer isto: GitHub - discourse/discourse-auth-proxy: An http proxy that uses the DiscourseConnect protocol to authenticate users

2 curtidas

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.