Configurar DiscourseConnect - Inicio de sesión único oficial para Discourse (sso)

DiscourseConnect is a core Discourse feature that allows you to configure “Single Sign-On (SSO)” to completely outsource all user registration and login from Discourse to another site. Offered to our pro, business and enterprise hosting customers.

:information_source: (Feb 2021) ‘Discourse SSO’ is now ‘DiscourseConnect’. If you are running an old version of Discourse, the settings below will be named sso_... rather than discourse_connect_...

The Problem

Many sites wishing to integrate with a Discourse site want to keep all user registration in a separate site. In such a setup all login operations should be outsourced to that different site.

What if I would like SSO in conjunction with existing auth?

The intention around DiscourseConnect is to replace Discourse authentication, if you would like to add a new provider see existing plugins such as: Discourse VK Authentication (vkontakte)

Enabling DiscourseConnect

To enable DiscourseConnect you have 3 settings you need to fill out:

enable_discourse_connect : must be enabled, global switch
discourse_connect_url: the offsite URL users will be sent to when attempting to log on
discourse_connect_secret: a secret string used to hash SSO payloads. Ensures payloads are authentic.

Once enable_discourse_connect is set to true:

  • Clicking on login or avatar will, redirect you to /session/sso which in turn will redirect users to discourse_connect_url with a signed payload.
  • Users will not be allowed to “change password”. That field is removed from the user profile.
  • Users will no longer be able to use Discourse auth (username/password, google, etc)

What if you check it by mistake?

See: Log back in as admin after locking yourself out with read-only mode or an invalid SSO configuration

Implementing DiscourseConnect on your site

:warning: Discourse uses emails to map external users to Discourse users, and assumes that external emails are secure. IF YOU DO NOT VALIDATE EMAIL ADDRESSES BEFORE SENDING THEM TO DISCOURSE, YOUR SITE WILL BE EXTREMELY VULNERABLE!

Alternatively, if you insist on sending unvalidated emails BE SURE to set require_activation=true, this will force all emails to be validated by Discourse. WE STILL STRONGLY ADVISE THAT YOU DO NOT DO THIS, so if you proceed with that setting enabled, you are assuming substantial risk.

Discourse will redirect clients to discourse_connect_url with a signed payload: (say discourse_connect_url is https://somesite.com/sso)

You will receive incoming traffic with the following

https://somesite.com/sso?sso=PAYLOAD&sig=SIG

The payload is a Base64 encoded string comprising of a nonce, and a return_sso_url. The payload is always a valid querystring.

For example, if the nonce is ABCD. raw_payload will be:

nonce=ABCD&return_sso_url=https%3A%2F%2Fdiscourse_site%2Fsession%2Fsso_login, this raw payload is base 64 encoded.

The endpoint being called must

  1. Validate the signature: ensure that HMAC-SHA256 of PAYLOAD (using discourse_connect_secret, as the key) is equal to the sig (sig will be hex encoded).
  2. Perform whatever authentication it has to
  3. Create a new url-encoded payload with at least nonce, email, and external_id. You can also provide some additional data, here’s a list of all keys that Discourse will understand:
    • nonce should be copied from the input payload
    • email must be a verified email address. If the email address has not been verified, set require_activation to “true”.
    • external_id is any string unique to the user that will never change, even if their email, name, etc change. The suggested value is your database’s ‘id’ row number.
    • username will become the username on Discourse if the user is new or SiteSetting.auth_overrides_username is set.
    • name will become the full name on Discourse if the user is new or SiteSetting.auth_overrides_name is set.
    • avatar_url will be downloaded and set as the user’s avatar if the user is new or SiteSetting.discourse_connect_overrides_avatar is set.
    • avatar_force_update is a boolean field. If set to true, it will force Discourse to update the user’s avatar, whether avatar_url has changed or not.
    • bio will become the contents of the user’s bio if the user is new, their bio is empty or SiteSetting.discourse_connect_overrides_bio is set.
    • Additional boolean (“true” or “false”) fields are: admin, moderator, suppress_welcome_message
  4. Base64 encode payload
  5. Calculate a HMAC-SHA256 hash of the payload using discourse_connect_secret as the key and Base64 encoded payload as text
  6. Redirect back to the return_sso_url with an sso and sig query parameter (http://discourse_site/session/sso_login?sso=payload&sig=sig)

Discourse will validate that the nonce is valid, and if valid, it will expire it right away so it can not be used again. Then, it will attempt to:

  1. Log the user on by looking up an already associated external_id in the SingleSignOnRecord model
  2. Log the user on by using the email provided (updating external_id) (unless require_activation = true)
  3. Create a new account for the user providing (email, username, name) updating external_id

Security concerns

The nonce (one time token) will expire automatically after 10 minutes. This means that as soon as the user is redirected to your site they have 10 minutes to log in / create a new account.

The protocol is safe against replay attacks as nonce may only be used once. The nonce is tied to the current browser session to protect against CSRF attacks.

Specifying group membership

If the discourse connect overrides groups option is specified, Discourse will consider the comma separated list of groups passed in groups.

Aside from groups, you may also specify group membership in your SSO payload using the add_groups and remove_groups attributes regardless of the discourse connect overrides groups option.

add_groups is a comma delimited list of group names we will ensure the user is a member of.
remove_groups is a comma delimited list of group names we will ensure the user is not a member of.

Reference implementation

Discourse contains a reference implementation of the SSO class:

A trivial implementation would be:

class DiscourseSsoController < ApplicationController
  def sso
    secret = "MY_SECRET_STRING"
    sso = DiscourseApi::SingleSignOn.parse(request.query_string, secret)
    sso.email = "user@email.com"
    sso.name = "Bill Hicks"
    sso.username = "bill@hicks.com"
    sso.external_id = "123" # unique id for each user of your application
    sso.sso_secret = secret

    redirect_to sso.to_url("http://l.discourse/session/sso_login")
  end
end

Transitioning to and from single sign on.

As long as the require_activation parameter is not set to true in the request payload, the system will trusts emails provided by the single sign on endpoint. This means that if you had an existing account in the past on Discourse with DiscourseConnect disabled, DiscourseConnect will simply re-use it and avoid creating a new account.

If you ever turn off DiscourseConnect, users will be able to reset passwords and gain access back to their accounts.

Real world example:

Given the following settings:

Discourse domain: http://discuss.example.com
DiscourseConnect url : http://www.example.com/discourse/sso
DiscourseConnect secret: d836444a9e4084d5b224a60c208dce14
Email validated: No (add require_activation=true to the payload)

User attempt to login

  • Nonce is generated: cb68251eefb5211e58c00ff1395f0c0b

  • Raw payload is generated: nonce=cb68251eefb5211e58c00ff1395f0c0b

  • Payload is Base64 encoded: bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI=

  • Payload is URL encoded: bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D

  • HMAC-SHA256 is generated on the Base64 encoded Payload: 1ce1494f94484b6f6a092be9b15ccc1cdafb1f8460a3838fbb0e0883c4390471

Finally browser is redirected to:

http://www.example.com/discourse/sso?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D&sig=1ce1494f94484b6f6a092be9b15ccc1cdafb1f8460a3838fbb0e0883c4390471

On the other end

  1. Payload is validated using HMAC-SHA256, if the sig mismatches, process aborts.
  2. By reversing the steps above nonce is extracted.

User logs in:

name: sam
external_id: hello123
email: test@test.com
username: samsam
require_activation: true

Unsigned payload is generated:

nonce=cb68251eefb5211e58c00ff1395f0c0b&name=sam&username=samsam&email=test%40test.com&external_id=hello123&require_activation=true

order does not matter, values are URL encoded

Payload is Base64 encoded

bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ==

Payload is URL encoded

bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ%3D%3D

Base64 encoded Payload is signed

3d7e5ac755a87ae3ccf90272644ed2207984db03cf020377c8b92ff51be3abc3

Browser redirects to:

http://discuss.example.com/session/sso_login?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGImbmFtZT1zYW0mdXNlcm5hbWU9c2Ftc2FtJmVtYWlsPXRlc3QlNDB0ZXN0LmNvbSZleHRlcm5hbF9pZD1oZWxsbzEyMyZyZXF1aXJlX2FjdGl2YXRpb249dHJ1ZQ%3D%3D&sig=3d7e5ac755a87ae3ccf90272644ed2207984db03cf020377c8b92ff51be3abc3

Synchronizing DiscourseConnect records

You can use the POST admin endpoint /admin/users/sync_sso to synchronize a DiscourseConnect record, pass it the same record you would pass to the DiscourseConnect endpoint, nonce does not matter.

If you call admin/users/sync_sso from another site, you will need to include a valid admin api_key and a valid api_username in the request’s headers. See Sync DiscourseConnect user data with the sync_sso route for more details about how to structure the request.

Clearing DiscourseConnect records

If your external_id values from your DiscourseConnect provider have changed (perhaps you changed the generation algorithm, perhaps it’s a different endpoint) you can safely remove all the existing records using the rails console:

SingleSignOnRecord.destroy_all

Logging off users

You can use the POST admin endpoint /admin/users/{USER_ID}/log_out to log out any user in the system if needed.

To configure the endpoint Discourse redirects to on logout search for the logout redirect setting. If no URL has been set here you will be redirected back to the URL configured in discourse connect url.

Search users by external_id

User profile data can be accessed using the /users/by-external/{EXTERNAL_ID}.json endpoint. This will return a JSON payload that contains the user information, including the user_id which can be used with the log_out endpoint.

Existing implementations

  • The discourse_api gem can be used for SSO. Have a look at the SSO code in its examples directory to see a basic implementation.

  • Our WordPress plugin makes it easy to configure SSO between WordPress and Discourse. Details about setting it up are found on the SSO tab of the plugin’s options page.

Future work

  • We would like to gather more reference implementations for SSO on other platforms. If you have one please post to the Dev / SSO category.

Advanced Features

  • You can pass through custom user fields by prefixing the field name with custom. For example custom.user_field_1 can be used to set the value of the UserCustomField that has the name user_field_1.
  • You can pass avatar_url to override user avatar (SiteSetting.discourse_connect_overrides_avatar needs to be enabled). Avatars are cached so pass avatar_force_update=true to force them to update if the url is the same. Right now, you can’t pass an empty url to disable users’ avatar.
  • By default the welcome message will be sent to all new users created through SSO. If you wish to suppress this you can pass suppress_welcome_message=true
  • To configure your Discourse instance as a Discourse connect provider see: Using DiscourseConnect as an identity provider.

Debugging your DiscourseConnect provider

To assist in debugging DiscourseConnect you may enable the site setting verbose_discourse_connect_logging. By enabling that site setting rich diagnostics will show up in YOURSITE.com/logs. Be sure to :white_check_mark: the warnings box at the bottom of YOURSITE.com/logs.

We will log a warning to the logs with a full dump of the SSO payload:

Last edited by @MarkDoerr 2025-06-17T19:20:06Z

Check documentPerform check on document:
173 Me gusta

Hola, me cuesta encontrarlo, pero ¿qué protocolo utiliza esto? ¿Asumo oauth2? Los parámetros no parecen coincidir y recibo un error del proveedor que parece aceptar un parámetro sso=? ¡Ayuda!

Gracias

DiscourseConnect es la implementación de SSO de Discourse. No utiliza un protocolo estándar.

Si no te importa mirar código PHP, hay un ejemplo de implementación aquí: wp-discourse/lib/sso-provider/discourse-sso.php at main · discourse/wp-discourse · GitHub.

Sí, eso no va a funcionar. Si tienes un proveedor OAuth2 que te gustaría usar para autenticar usuarios, echa un vistazo al plugin Discourse OAuth2 Basic.

1 me gusta

Gracias @simon. ¿El código PHP también es un proveedor y no un consumidor? También vi un proveedor OIDC que podría funcionar y también un proveedor “intermediario” en el área de plugins.

Si el proveedor SSO no es estándar, ¿para quién/qué está destinado si no funciona bien con otros?

¡Gracias de nuevo!

El código al que enlacé es para usar WordPress como proveedor de autenticación para Discourse.

El plugin de WordPress también permite que WordPress se use como cliente de DiscourseConnect: wp-discourse/lib/sso-client at main · discourse/wp-discourse · GitHub.

No estoy seguro de cuál fue la motivación para agregar una implementación SSO personalizada a Discourse. Supongo que hubo un caso de negocio para ello.

Un beneficio que proporciona es que permite que un sitio externo se integre estrechamente con Discourse. Por ejemplo, todos los atributos de usuario enumerados aquí se pueden sincronizar con Discourse durante el proceso de autenticación: discourse/lib/discourse_connect_base.rb at 7b89fdead98606d4f47ceb0a1d240d0f6e5f589e · discourse/discourse · GitHub.

También permite que se utilicen sitios que no están configurados para ser proveedores OAuth2 u OpenID Connect para autenticar usuarios en Discourse.

La desventaja es que requiere agregar algo de código personalizado al sitio proveedor de autenticación.

1 me gusta

Hola, tengo curiosidad sobre los problemas de no verificar las direcciones de correo electrónico en un sitio externo que proporciona SSO. ¿Es solo que permite el spam automatizado? ¿O hay otras consideraciones? ¿Por qué no se recomienda que Discourse maneje la verificación de correo electrónico si el sitio externo no lo hace?

Gracias por cualquier información adicional.

El peor escenario que conozco requiere estas condiciones:

  • las direcciones de correo electrónico no están verificadas en el sitio externo
  • require_activation=true no está configurado en la carga útil de SSO
  • existen cuentas en el sitio de Discourse que no tienen un SingleSignOnRecord asociado con ellas (el propietario de la cuenta nunca ha iniciado sesión en Discourse con SSO)

En ese caso, alguien podría registrarse en el sitio externo utilizando la dirección de correo electrónico de un usuario de Discourse que nunca ha iniciado sesión con SSO. Esto permitiría que la cuenta no verificada del sitio externo se apodere de la cuenta de Discourse que utiliza la misma dirección de correo electrónico. Esto sería especialmente preocupante si se tratara de una cuenta de administrador en Discourse.

Sí se recomienda que Discourse maneje la verificación de correo electrónico si el sitio externo no lo hace:

Sin embargo, hay un par de razones por las que es mejor manejar la verificación de correo electrónico en el sitio externo:

  • obligar a los usuarios a recibir el correo electrónico de confirmación de Discourse añade algo de fricción cuando los usuarios intentan iniciar sesión en Discourse por primera vez. (En realidad, esa fricción tiene que ocurrir en algún lugar, ya sea en el lado de Discourse o en el lado del sitio externo).
  • Discourse no emparejará las cuentas de Discourse existentes con los inicios de sesión externos basándose en la dirección de correo electrónico si require_activation está configurado como true en la carga útil de SSO. Esto es un problema si habilitas DiscourseConnect después de que se hayan creado algunas cuentas en Discourse registrándose con nombre de usuario/contraseña. También es un problema si por alguna razón necesitas eliminar entradas de SingleSignOnRecord en Discourse. Discourse no creará automáticamente nuevas entradas de SingleSignOnRecord cuando los usuarios intenten volver a iniciar sesión en Discourse.
4 Me gusta

Gracias, @simon, ¡eso es muy útil!

Hola, tengo una pregunta sobre el campo groups en la carga útil SSO.

¿Se anularán también los grupos automatizados (como administradores, moderadores y niveles de confianza)? ¿O se conservarán?

¡No! Suponiendo que la descripción de esa configuración sea correcta, solo afectará a los grupos manuales.

Sabes qué… no vi la palabra “manual” en la descripción. Suena prometedor para mi caso de uso, lo intentaré y te informaré.

1 me gusta

Cuando leí esto, pensé que se suponía que debía generar la firma directamente a partir de la carga útil codificada en base64. No me di cuenta de que necesitas generarla a partir de los bytes UTF-8. ¿Se podría aclarar esto?

He estado jugando con DiscourseConnect - la documentación es genial, gracias.
Sin embargo, he encontrado algunos obstáculos sobre los que espero ayuda/aclaración.

Queremos que los usuarios puedan iniciar sesión en Wordpress usando su inicio de sesión de Discourse (eso funciona bien :)) - sin embargo.

  • ¿Es posible crear un usuario de Wordpress a través del registro de Discourse (cuando un usuario crea una cuenta de usuario de Discourse, se crea automáticamente una cuenta/perfil de Wordpress para ellos para que puedan iniciar sesión en Wordpress)?

  • ¿Es posible sincronizar los grupos de usuarios de Wordpress y los grupos de Discourse?
    Si un usuario tiene una cuenta de usuario tanto en Wordpress como en Discourse, DiscourseConnect puede unirlas, pero no otorga a la cuenta de Wordpress los grupos de usuarios de los grupos de Discourse del mismo nombre, y viceversa (y ¿qué pasa si los grupos no tienen el mismo nombre? ¿Cómo puedo decirle a DiscourseConnect que otorgue el Grupo de Usuarios ‘Grupo de Pruebas’ cuando el usuario tiene el Grupo de Discourse ‘Grupo para Probar Cosas’?

¿Qué me estoy perdiendo?

No sé la respuesta, pero…

Ten cuidado con eso. Es algo ilegal y se considera una mala práctica (fuera de los propietarios del sitio, por supuesto :smirking_face:) porque sucedería sin el consentimiento y conocimiento del usuario, y al mismo tiempo los datos se trasladarían a otro lugar.

Claro, eso está en una zona gris y, básicamente, por ejemplo, Google lo está haciendo.

Pero… ¿por qué? Limita el inicio de sesión solo a Discourse SSO en el lado de WordPress y redirige a los usuarios a Discourse para la creación de cuentas y eso es todo. Pero, por lo que sé, no puedes sincronizar cuentas de usuario automáticamente de inmediato. Y, ¿por qué lo harías, porque con SSO sucede cuando un usuario lo necesita?

En nuestro escenario (como organización de membresía)

  • Wordpress se utiliza para gestionar suscripciones, comprar artículos en la tienda de Wordpress, y usamos Grupos de Usuarios para gestionar lo que un miembro puede hacer en la organización
  • Discourse es nuestro foro/comunidad en línea y usamos Grupos para controlar a qué áreas de Discourse puede acceder un usuario)

Actualmente, un nuevo miembro necesita configurar una cuenta de Wordpress (y configurar su suscripción, etc.) y también configurar una cuenta de Discourse, y los Grupos de Usuarios-Grupos de Discourse se gestionan/sincronizan manualmente.

Estoy intentando encontrar una solución donde un nuevo usuario configure una sola vez para crear ambas cuentas, y los Grupos de Usuarios-Grupos de Discourse se sincronicen automáticamente; estoy seguro de que puedo resolver la sincronización de grupos con APIs, etc. Es la configuración de cuentas de usuario múltiple lo que intento resolver/evitar.

Parece que lo que estás haciendo es usar Discourse como el proveedor SSO para WordPress. Ese enfoque se describe aquí: Usar Discourse como proveedor de identidad (SSO, DiscourseConnect). El plugin de Discourse para WordPress tiene opciones para usar WordPress como proveedor SSO para Discourse, o para usar Discourse como proveedor de identidad para WordPress. Usar el mismo nombre para ambos enfoques genera cierta confusión.

Me inclinaría a usar WordPress como el proveedor de identidad para este caso. Con ese enfoque, los usuarios crearán cuentas en tu sitio de WordPress y luego iniciarán sesión en Discourse con sus credenciales de WordPress. Una cosa a tener en cuenta con este enfoque es que significa que los usuarios solo podrán iniciar sesión en Discourse a través de WordPress, no será posible crear una cuenta de Discourse sin tener ya una cuenta de WordPress. Creo que esa es la configuración apropiada al integrar Discourse con un sitio de membresía de WordPress.

Cuando WordPress se utiliza como proveedor de identidad para Discourse, hay un par de funciones de utilidad que son útiles para establecer las membresías de grupo de Discourse de un usuario basándose en su actividad en WordPress. Esas funciones se describen aquí: Gestionar membresías de grupo en Discourse con WP Discourse SSO.

Volviendo a tu pregunta original:

Ha pasado un tiempo desde que miré el código del cliente DiscourseConnect del plugin de WordPress, pero creo que lo que estás pidiendo es más o menos la forma en que se espera que funcione ese código. Si un usuario tiene una cuenta de Discourse, solo necesita hacer clic en el enlace “Iniciar sesión a través de Discourse” en WordPress y se creará una cuenta para él.

Esto sería técnicamente posible al usar WordPress como Cliente de DiscourseConnect, pero a menos que algo haya cambiado, no podrás usar los métodos add_user_to_discourse_group y remove_user_from_discourse_group que se describen en la documentación que enlacé. Necesitarías hacer algo como configurar un Webhook de Discourse que se activara cuando un usuario se agregara a un grupo de Discourse, luego agregar algo de código en WordPress para procesar ese webhook. Para sincronizar grupos de WordPress a Discourse, necesitarías hacer una llamada a la API de Discourse para actualizar los grupos de un usuario cuando hubiera un cambio en WordPress. Por lo tanto, algo que sería bastante fácil de lograr si usas WordPress como proveedor de DiscourseConnect podría ser algo complicado si usas WordPress como cliente de DiscourseConnect.

1 me gusta

Excepto si se utiliza el inicio de sesión personalizado, como suele ser la situación con WooCommerce/membresías/LLM que muestran ese botón y obligan a usar solo Discourse SSO como proveedor no se está haciendo de forma predeterminada y necesita algo de trabajo personalizado.

Hay un par de problemas posibles, uno relacionado con el almacenamiento en caché y otro relacionado con las redirecciones de inicio de sesión que añaden algunos complementos. Cualquiera que tenga problemas debería preguntar sobre ellos en la categoría Support > WordPress. Suelen resolverse fácilmente.

Se me olvidó por completo informar: De hecho, funciona exactamente como se describe, solo los grupos manuales se ven afectados.

Hola @simon,

Creo que realmente necesito ayuda con la respuesta 404 de mi POC de esta función SSO. He estado trabajando en este problema durante todo el día, pero todavía no puedo averiguar cuál es el problema. Pude:

  1. Habilitar discourse_connect
  2. Establecer discourse_connect_url en https:/ /localhost:4200/login
  3. Secreto de discourse-connect: 20’s (1) 1111111111111111111 para simplificarlo

Luego, en la página de inicio de sesión de mi aplicación Angular, envié esta solicitud siguiendo el código:


pero obtuve la respuesta 404 no encontrada.

Para mi entendimiento, al enviar una solicitud POST al endpoint admin/users/sync_sso, si el usuario no existía en Discourse, debería crear un nuevo usuario basándose en user_id y email, luego el resultado devuelto debería ser un objeto de usuario, no un objeto vacío con código de estado 404.
También revisé el registro y no proporcionó ninguna información relacionada para esta respuesta fallida.

¡Gracias de antemano!