Configuration DiscourseConnect - Authentification unique officielle pour 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 « J'aime »

Bonjour, j’ai du mal à trouver, mais quel protocole utilise-t-il ? Est-ce que l’on suppose que c’est oauth2 ? Les paramètres ne semblent pas correspondre, et j’obtiens une erreur du fournisseur qui semble accepter un paramètre sso= ? Aidez-moi !

Merci

DiscourseConnect est l’implémentation de Discourse du SSO. Il n’utilise pas de protocole standard.

Si cela ne vous dérange pas de regarder du code PHP, il existe un exemple d’implémentation ici : wp-discourse/lib/sso-provider/discourse-sso.php at main · discourse/wp-discourse · GitHub.

Oui, cela ne fonctionnera pas. Si vous avez un fournisseur OAuth2 que vous souhaitez utiliser pour authentifier les utilisateurs, jetez un œil au plugin Discourse OAuth2 Basic.

1 « J'aime »

Merci @simon. Le code PHP est-il également un fournisseur et non un consommateur ? J’ai également vu un fournisseur OIDC qui pourrait fonctionner et aussi un fournisseur « intermédiaire » dans la section des plugins.

Si le fournisseur SSO n’est pas standard, à qui/quoi est-il destiné s’il ne fonctionne pas bien avec les autres ?

Merci encore !

Le code que j’ai lié sert à utiliser WordPress comme fournisseur d’authentification pour Discourse.

Le plugin WordPress permet également à WordPress d’être utilisé comme client DiscourseConnect : wp-discourse/lib/sso-client at main · discourse/wp-discourse · GitHub.

Je ne suis pas sûr de la motivation derrière l’ajout d’une implémentation SSO personnalisée à Discourse. Je suppose qu’il y avait une raison commerciale.

L’un des avantages est qu’il permet d’intégrer étroitement un site externe à Discourse. Par exemple, tous les attributs utilisateur listés ici peuvent être synchronisés avec Discourse pendant le processus d’authentification : discourse/lib/discourse_connect_base.rb at 7b89fdead98606d4f47ceb0a1d240d0f6e5f589e · discourse/discourse · GitHub.

Il permet également d’utiliser des sites qui ne sont pas configurés pour être des fournisseurs OAuth2 ou OpenID Connect pour authentifier les utilisateurs sur Discourse.

L’inconvénient est qu’il nécessite d’ajouter du code personnalisé sur le site du fournisseur d’authentification.

1 « J'aime »

Salut, je suis curieux de savoir quels sont les problèmes liés à la non-vérification des adresses e-mail sur un site externe qui fournit le SSO. Est-ce simplement que cela permet le spam automatisé ? Ou y a-t-il d’autres considérations ? Pourquoi n’est-il pas recommandé que Discourse gère la vérification des e-mails si le site externe ne le fait pas ?

Merci pour tout éclairage supplémentaire.

Le pire scénario dont j’ai connaissance nécessite ces conditions :

  • les adresses e-mail ne sont pas vérifiées sur le site externe
  • require_activation=true n’est pas défini dans la charge utile SSO
  • il existe des comptes existants sur le site Discourse qui n’ont pas de SingleSignOnRecord associé (le propriétaire du compte ne s’est jamais connecté à Discourse avec SSO)

Dans ce cas, quelqu’un pourrait s’inscrire sur le site externe en utilisant l’adresse e-mail d’un utilisateur Discourse qui ne s’est jamais connecté avec SSO. Cela permettrait au compte non vérifié du site externe de prendre le contrôle du compte Discourse utilisant la même adresse e-mail. Ce serait particulièrement préoccupant s’il s’agissait d’un compte administrateur sur Discourse.

Il est recommandé que Discourse gère la vérification des e-mails si le site externe ne le fait pas :

Il y a cependant quelques raisons pour lesquelles il est préférable de gérer la vérification des e-mails sur le site externe :

  • forcer les utilisateurs à recevoir l’e-mail de confirmation de Discourse ajoute une certaine friction lorsque les utilisateurs tentent de se connecter pour la première fois à Discourse. (Réaliste, cette friction doit se produire quelque part - soit du côté de Discourse, soit du côté du site externe.)
  • Discourse ne fera pas correspondre les comptes Discourse existants aux connexions externes en fonction de l’adresse e-mail si require_activation est défini sur true dans la charge utile SSO. C’est un problème si vous activez DiscourseConnect après que certains comptes ont été créés sur Discourse en s’inscrivant avec un nom d’utilisateur/mot de passe. C’est aussi un problème si, pour une raison quelconque, vous devez un jour supprimer les entrées SingleSignOnRecord sur Discourse. Discourse ne créera pas automatiquement de nouvelles entrées SingleSignOnRecord lorsque les utilisateurs tenteront de se reconnecter à Discourse.
4 « J'aime »

Merci, @simon - c’est très utile !

Bonjour, j’ai une question concernant le champ groups dans la charge utile SSO.

Les groupes automatisés (comme les administrateurs, les modérateurs et les niveaux de confiance) seront-ils également écrasés ? Ou seront-ils conservés ?

Non ! En supposant que la description de ce paramètre soit correcte, cela n’affectera que les groupes manuels.

Vous savez quoi… Je n’ai pas vu le mot « manuel » dans la description. Ça semble prometteur pour mon cas d’utilisation, je vais essayer et je vous dirai.

1 « J'aime »

Lorsque j’ai lu ceci, j’ai cru que je devais générer la signature directement à partir de la charge utile encodée en base64. Je n’avais pas réalisé qu’il fallait la générer à partir des octets UTF-8. Cela pourrait-il être clarifié ?

J’ai joué avec DiscourseConnect - la documentation est excellente, merci.
Cependant, j’ai rencontré quelques obstacles pour lesquels j’espère obtenir de l’aide/des éclaircissements.

Nous voulons que les utilisateurs puissent se connecter à WordPress en utilisant leur connexion Discourse (cela fonctionne bien :slight_smile: ) - cependant.

  • Est-il possible de créer un utilisateur WordPress via l’inscription Discourse (lorsqu’un utilisateur crée un compte utilisateur Discourse, il crée automatiquement un compte/profil WordPress pour lui afin qu’il puisse se connecter à WordPress)

  • Est-il possible de synchroniser les groupes d’utilisateurs WordPress et les groupes Discourse.
    Si un utilisateur a un compte utilisateur WordPress et Discourse, DiscourseConnect est capable de les joindre - mais ne donne pas au compte WordPress les groupes d’utilisateurs des groupes Discourse du même nom, et vice versa (et qu’en est-il si les groupes n’ont pas le même nom - comment puis-je dire à DiscourseConnect de donner le groupe d’utilisateurs ‘Groupe de test’ lorsque l’utilisateur a le groupe Discourse ‘Groupe pour tester des choses’ ?

Qu’est-ce qui me manque ?

Je ne connais pas la réponse, mais…

Soyez prudent avec ça. C’est quelque chose d’illégal et considéré comme une mauvaise pratique (en dehors des propriétaires de sites, bien sûr :smirking_face:) car cela se produirait sans le consentement et la connaissance de l’utilisateur, et en même temps les données seraient déplacées ailleurs.

Bien sûr, c’est un peu une zone grise et, fondamentalement, Google fait cela, par exemple.

Mais… pourquoi ? Limitez la connexion uniquement à Discourse SSO côté WordPress et redirigez les utilisateurs vers Discourse pour la création de compte et c’est tout. Mais à ma connaissance, vous ne pouvez pas synchroniser les comptes d’utilisateurs automatiquement dès la sortie de la boîte. Et pourquoi le feriez-vous, car avec le SSO, cela se produit quand un utilisateur en a besoin.

Dans notre scénario (en tant qu’organisation membre)

  • Wordpress est utilisé pour gérer les abonnements, acheter des articles sur la boutique Wordpress, et nous utilisons les Groupes d’utilisateurs pour gérer ce qu’un membre peut faire dans l’organisation
  • Discourse est notre forum/communauté en ligne et nous utilisons les Groupes pour contrôler les zones de Discourse auxquelles un utilisateur a accès)

Actuellement, un nouveau membre doit créer un compte Wordpress (et configurer son abonnement, etc.) et également créer un compte Discourse, et les Groupes d’utilisateurs-Groupes Discourse sont gérés/synchronisés manuellement.

J’essaie de trouver une solution où un nouvel utilisateur effectue une seule configuration pour créer les deux comptes, et les Groupes d’utilisateurs-Groupes Discourse sont synchronisés automatiquement - je suis sûr que je peux résoudre la synchronisation des groupes avec des API, etc. C’est la configuration multi-comptes utilisateur que j’essaie de résoudre/éviter.

Il semble que vous utilisiez Discourse comme fournisseur SSO pour WordPress. Cette approche est décrite ici : Utiliser Discourse comme fournisseur d’identité (SSO, DiscourseConnect). Le plugin Discourse WordPress offre des options pour utiliser WordPress comme fournisseur SSO pour Discourse, ou pour utiliser Discourse comme fournisseur d’identité pour WordPress. Utiliser le même nom pour les deux approches entraîne une certaine confusion.

Je serais tenté d’utiliser WordPress comme fournisseur d’identité dans ce cas. Avec cette approche, les utilisateurs créeront des comptes sur votre site WordPress, puis se connecteront à Discourse avec leurs identifiants WordPress. Une chose à savoir avec cette approche est que cela signifie que les utilisateurs ne pourront se connecter à Discourse que via WordPress, il ne sera pas possible de créer un compte Discourse sans avoir déjà un compte WordPress. Je pense que c’est la configuration appropriée lors de l’intégration de Discourse avec un site d’adhésion WordPress.

Lorsque WordPress est utilisé comme fournisseur d’identité pour Discourse, il existe quelques fonctions utilitaires qui sont utiles pour définir les appartenances aux groupes Discourse des utilisateurs en fonction de leur activité sur WordPress. Ces fonctions sont décrites ici : Gérer l’appartenance aux groupes dans Discourse avec WP Discourse SSO.

Pour revenir à votre question initiale :

Cela fait un moment que je n’ai pas examiné le code du client DiscourseConnect du plugin WordPress, mais je pense que ce que vous demandez est plus ou moins la manière dont ce code est censé fonctionner. Si un utilisateur a un compte Discourse, il lui suffit de cliquer sur le lien “Se connecter via Discourse” sur WordPress et un compte sera créé pour lui.

Ce serait techniquement possible en utilisant WordPress comme client DiscourseConnect, mais à moins que quelque chose n’ait changé, vous ne pourrez pas utiliser les méthodes add_user_to_discourse_group et remove_user_from_discourse_group décrites dans la documentation que j’ai liée. Vous devriez faire quelque chose comme configurer un Webhook Discourse qui serait déclenché lorsqu’un utilisateur est ajouté à un groupe Discourse, puis ajouter du code sur WordPress pour traiter ce webhook. Pour synchroniser les groupes de WordPress vers Discourse, vous devriez faire un appel API à Discourse pour mettre à jour les groupes d’un utilisateur lorsqu’il y a un changement sur WordPress. Donc, quelque chose qui serait assez facile à réaliser si vous utilisez WordPress comme fournisseur DiscourseConnect pourrait être quelque peu compliqué si vous utilisez WordPress comme client DiscourseConnect.

1 « J'aime »

Sauf si une connexion personnalisée est utilisée, comme c’est généralement le cas avec WooCommerce/memberships/LLM qui affichent ce bouton et forcent l’utilisation du seul SSO Discourse comme fournisseur n’est pas le cas par défaut et nécessite un travail personnalisé.

Il y a quelques problèmes possibles, l’un lié à la mise en cache et l’autre aux redirections de connexion ajoutées par certains plugins. Ceux qui rencontrent ces problèmes devraient en parler dans la catégorie Support > WordPress. Ils sont généralement faciles à résoudre.

J’ai complètement oublié de faire mon rapport : en effet, cela fonctionne exactement comme décrit, seuls les groupes manuels sont affectés.

Salut @simon,
Je pense que j’ai vraiment besoin d’aide concernant la réponse 404 de mon POC pour cette fonctionnalité SSO. Je travaille sur ce problème depuis une journée entière, mais je n’arrive toujours pas à trouver le problème. J’ai pu :

  1. Activer discourse_connect
  2. Définir discourse_connect_url sur https://localhost:4200/login
  3. Secret discourse-connect : 20’s (1) 1111111111111111111 pour simplifier

Ensuite, sur ma page de connexion de mon application Angular, j’ai envoyé cette requête en suivant le code :


mais j’ai reçu la réponse 404 non trouvée.

Pour ma compréhension, lors de l’envoi d’une requête POST à l’endpoint admin/users/sync_sso, si l’utilisateur n’existait pas dans discourse, il devrait créer un nouvel utilisateur basé sur user_id et email, puis le résultat retourné devrait être un objet utilisateur et non un objet vide avec un code d’état 404.
J’ai également vérifié le journal, il n’a fourni aucune information pertinente pour cette réponse échouée.

Merci d’avance !