¿Puedo usar la API de Discourse para autenticar usuarios en otra aplicación?

Tengo un foro de Discourse funcionando, donde los usuarios pueden crear un inicio de sesión local (nombre de usuario + contraseña).

Me gustaría reutilizar su nombre de usuario y contraseña en otra aplicación. En otras palabras: los usuarios introducirían su nombre de usuario y contraseña en la otra aplicación, y la aplicación debería poder comprobar si se trata de un inicio de sesión válido para el foro.

He estado leyendo la documentación de la API de Discourse. Muchas cosas son posibles, incluido el establecimiento del nombre de usuario y la contraseña para un usuario determinado, pero no encontré un punto final de API para validar un nombre de usuario y contraseña existentes contra la lista de usuarios del foro.

Supongo que debe existir un punto final de API de este tipo, ya que el foro debe poder hacerlo para iniciar sesión de un usuario a través de la interfaz web.

¿Cuál es el punto final de la API para comprobar un nombre de usuario y una contraseña para iniciar sesión en el foro?

3 Me gusta

Más directamente, podría usar DiscourseConnect como un mecanismo para validar usuarios o, delante de su aplicación, usar discourse-auth-proxy.

Estos son métodos sugeridos para autenticar usuarios en lugar de manejar credenciales de inicio de sesión directamente. También significa que no necesita intentar manejar los detalles de 2FA.

5 Me gusta

En realidad, mi “otra aplicación” es una aplicación de escritorio, no una aplicación web. No creo que discourse-auth-proxy funcione en ese caso.

En la página de DiscourseConnect, una de las primeras afirmaciones es esta:

Muchos sitios que desean integrarse con un sitio de Discourse quieren mantener todo el registro de usuarios en un sitio separado. En tal configuración, todas las operaciones de inicio de sesión deben ser externalizadas a ese sitio diferente.

Esto es exactamente lo contrario de lo que quiero hacer: quiero externalizar todas las operaciones de inicio de sesión a Discourse. ¿Hay alguna forma de usar DiscourseConnect para hacer eso?

1 me gusta

Sí, absolutamente.

Lo complicado es que hay un secreto compartido entre el proveedor (Discourse) y el consumidor (tu aplicación). Si distribuyes tu aplicación, los usuarios tendrán acceso a todos los secretos en ella.

Poner un proxy de autenticación delante de un servicio web mínimo personalizado que proporcione un token firmado a tu aplicación podría funcionar bien.

Estoy seguro de que hay otras formas de hacerlo en las que no estoy pensando.

¿Te refieres a la clave API? Parece posible crear una clave API “granular”, que solo tenga acceso a puntos finales de API específicos. Todavía no me queda claro qué puntos finales se requerirían si utilizo ese enfoque. ¿Lo sabes?

Sí, un servicio web mínimo con un proxy de autenticación podría ser una buena solución; tendré que experimentar un poco para averiguarlo.

No exactamente: sería el valor de discourse connect provider secrets para la aplicación, que debería configurarse junto con enable discourse connect provider.

Aquí se explica más información al respecto: Use Discourse as an identity provider (SSO, DiscourseConnect)

1 me gusta

Para una aplicación de escritorio, quizás este método utilizado en este ejemplo de React Native pueda serte útil:

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

1 me gusta

Si entiendo esto correctamente, este método significaría que el usuario inicia sesión usando un navegador. Eso puede funcionar, aunque esperaba encontrar un método donde el nombre de usuario y la contraseña se puedan ingresar en nuestra aplicación de escritorio, sin abrir un navegador.

Entiendo que el enfoque que tengo en mente no admitirá TFA a menos que lo implemente yo mismo, y que no admitirá inicios de sesión a través de proveedores externos (Google, Facebook, Discord, …)

No creo que esto sea compatible explícitamente, pero crearía una sesión de la misma manera que lo haría el usuario en el 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

y duplica esta lógica de inicio de sesión en la aplicación.

Según entiendo en este momento, parece que el método utilizado en el ejemplo de Reactive Native se puede transponer a nuestra aplicación de escritorio (que está en Python).

El punto de acceso a la API utilizado parece ser <site>/session, y toma un nombre de usuario, una contraseña y un token csrf. El token csrf se puede obtener de <site>/session/csrf.

Esto está muy cerca de lo que estaba buscando. Creo que lo intentaré y volveré a informar si me funciona.

¿Está documentado en algún lugar el punto de acceso a la API <site>/session?

1 me gusta

La mejor manera de conseguir lo que quieres en una aplicación de escritorio es usar las Claves de API de Usuario.

Necesitas una interfaz web, ya sea en la aplicación o abriendo el navegador, pero si haces que tu aplicación sea un manejador del protocolo utilizado por las aplicaciones móviles, puedes hacer que obtenga fácilmente el token de esa manera y solo tengas que usar el navegador de nuevo si el token expira o si usan un dispositivo diferente.

Mi experiencia personal con esto es que usar las claves de API de usuario es una opción mucho más segura y sencilla que intentar usar los puntos finales de sesión. :slight_smile:

2 Me gusta

Aquí hay 20 líneas de código Python que hacen aproximadamente lo mismo que el código de React Native al que se refiere @renato (excepto que no hay compatibilidad con Discourse 2.5; no lo necesito).

Funciona bien, asumiendo que estás utilizando el inicio de sesión básico basado en nombre de usuario y contraseña. Aún así, investigaré los métodos alternativos, utilizando el inicio de sesión SSO de Discourse según lo configurado en la instancia de 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)

He intentado aplicar esto pero no consigo que funcione. A continuación, se muestra un código Python (simplificado) que genera una URL para .../session/sso_provider. Cuando lo intento, obtengo Login Error. No tengo idea de qué significa eso.

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 ejemplo, una ejecución podría generar la URL en el comando curl a continuación:

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

Como administrador, habilita verbose discourse connect logging, pruébalo y luego revisa /logs en tu foro para ver errores más detallados, por ejemplo: https://forum.embeetle.com/logs

Verás, por ejemplo:

Necesitas firmar el payload, no el payload citado, por ejemplo:

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

¡Y entonces funciona!

Por cierto, necesitas revocar y cambiar este secreto inmediatamente porque cualquiera que lo tenga puede iniciar sesión en tu aplicación, como acabo de hacer mientras probaba esto.

3 Me gusta

No te preocupes, la página a la que estoy redirigiendo es una página pública, solo para pruebas.

De todos modos, cambié el secreto.

2 Me gusta

¿Cuáles son tus 3 principales sugerencias de código abierto para esa tarea específica?

Estoy usando Nginx, pero probablemente eso podría gestionarse con un desarrollo más robusto como Keycloak.

Por auth-proxy, me refiero a esto: GitHub - discourse/discourse-auth-proxy: An http proxy that uses the DiscourseConnect protocol to authenticate users

2 Me gusta

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