Configurazione DiscourseConnect - Single Sign-On ufficiale per 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 Mi Piace

Ciao, è difficile per me trovare informazioni, ma quale protocollo utilizza? Si presume oauth2? I parametri non sembrano corrispondere e ricevo un errore dal provider che sembra accettare un parametro sso=? Aiuto!

grazie

DiscourseConnect è l’implementazione di SSO di Discourse. Non utilizza un protocollo standard.

Se non ti dispiace guardare il codice PHP, c’è un esempio di implementazione qui: wp-discourse/lib/sso-provider/discourse-sso.php at main · discourse/wp-discourse · GitHub.

Sì, non funzionerà. Se hai un provider OAuth2 che vorresti utilizzare per autenticare gli utenti, dai un’occhiata al plugin Discourse OAuth2 Basic.

1 Mi Piace

Grazie @simon. Anche il codice PHP è un provider e non un consumer? Ho anche visto un provider OIDC che potrebbe funzionare e anche un provider “intermediario” nell’area dei plugin.

Se il provider SSO non è standard, allora per chi/cosa è inteso se non funziona bene con gli altri?

Grazie ancora!

Il codice a cui ho fatto riferimento serve per usare WordPress come provider di autenticazione per Discourse.

Il plugin WordPress permette anche a WordPress di essere usato come client DiscourseConnect: wp-discourse/lib/sso-client at main · discourse/wp-discourse · GitHub.

Non sono sicuro quale fosse la motivazione per aggiungere un’implementazione SSO personalizzata a Discourse. Immagino ci fosse un caso aziendale per questo.

Un vantaggio che fornisce è che permette a un sito esterno di essere strettamente integrato con Discourse. Ad esempio, tutti gli attributi utente elencati qui possono essere sincronizzati con Discourse durante il processo di autenticazione: discourse/lib/discourse_connect_base.rb at 7b89fdead98606d4f47ceb0a1d240d0f6e5f589e · discourse/discourse · GitHub.

Permette anche a siti che non sono configurati per essere provider OAuth2 o OpenID Connect di essere usati per autenticare gli utenti su Discourse.

Lo svantaggio è che richiede l’aggiunta di codice personalizzato al sito del provider di autenticazione.

1 Mi Piace

Ciao, sono curioso di sapere quali siano i problemi derivanti dalla mancata verifica degli indirizzi email su un sito esterno che fornisce l’SSO. Si tratta solo di abilitare lo spam automatico? O ci sono altre considerazioni? Perché non è consigliabile che Discourse gestisca la verifica delle email se il sito esterno non lo fa?

Grazie per qualsiasi ulteriore approfondimento.

Lo scenario peggiore di cui sono a conoscenza richiede queste condizioni:

  • gli indirizzi email non sono verificati sul sito esterno
  • require_activation=true non è impostato nel payload SSO
  • ci sono account esistenti sul sito Discourse che non hanno un SingleSignOnRecord associato (il proprietario dell’account non ha mai effettuato l’accesso a Discourse tramite SSO)

In quel caso, qualcuno potrebbe registrarsi sul sito esterno utilizzando l’indirizzo email di un utente Discourse che non ha mai effettuato l’accesso tramite SSO. Ciò consentirebbe all’account non verificato dal sito esterno di impossessarsi dell’account Discourse che utilizza lo stesso indirizzo email. Ciò sarebbe particolarmente preoccupante se si trattasse di un account amministratore su Discourse.

È consigliato che Discourse gestisca la verifica dell’email se il sito esterno non la gestisce:

Ci sono un paio di ragioni per cui è meglio gestire la verifica dell’email sul sito esterno:

  • forzare gli utenti a ricevere l’email di conferma da Discourse aggiunge un attrito quando gli utenti tentano per la prima volta di accedere a Discourse. (Realisticamente, quell’attrito deve comunque avvenire da qualche parte, o da parte di Discourse o da parte del sito esterno.)
  • Discourse non assocerà gli account Discourse esistenti agli accessi esterni in base all’indirizzo email se require_activation è impostato su true nel payload SSO. Questo è un problema se abiliti DiscourseConnect dopo che alcuni account sono stati creati su Discourse registrandosi con nome utente/password. È anche un problema se per qualsiasi motivo devi eliminare le voci SingleSignOnRecord su Discourse. Discourse non creerà automaticamente nuove voci SingleSignOnRecord quando gli utenti tenteranno di accedere nuovamente a Discourse.
4 Mi Piace

Grazie, @simon, è molto utile!

Ciao, ho una domanda riguardo al campo groups nel payload SSO.

Anche i gruppi automatizzati (come amministratori, moderatori e livelli di fiducia) verranno sovrascritti? O verranno mantenuti?

No! Supponendo che la descrizione di tale impostazione sia corretta, influenzerà solo i gruppi manuali.

Sai cosa… non ho visto la parola “manuale” nella descrizione. Sembra promettente per il mio caso d’uso, ci proverò e ti farò sapere.

1 Mi Piace

Quando ho letto questo, pensavo di dover generare la firma direttamente dal payload codificato in base64. Non mi ero reso conto che fosse necessario generarla dai byte UTF-8. Potrebbe essere chiarito?

Sto giocando con DiscourseConnect: la documentazione è ottima, grazie.
Tuttavia, ho riscontrato alcuni ostacoli per i quali spero di ricevere aiuto/chiarimenti.

Vogliamo che gli utenti possano accedere a WordPress utilizzando il loro login Discourse (che funziona bene :slight_smile: ) - tuttavia.

  • È possibile creare un utente WordPress tramite la registrazione a Discourse (quando un utente crea un account utente Discourse, crea automaticamente un account/profilo WordPress per loro in modo che possano accedere a WordPress)

  • È possibile sincronizzare i gruppi utente di WordPress e i gruppi Discourse.
    Se un utente ha sia un account utente WordPress che Discourse, DiscourseConnect è in grado di unirli, ma non assegna all’account WordPress i gruppi utente dei gruppi Discourse con lo stesso nome, e viceversa (e cosa succede se i gruppi non hanno lo stesso nome - come posso dire a DiscourseConnect di assegnare il gruppo utente ‘Testing Group’ quando l’utente ha il gruppo Discourse ‘Group for Testing Things’?

Cosa mi sto perdendo?

Non conosco la risposta, ma…

Fai attenzione. È da qualche parte illegale e una pratica considerata ampiamente scorretta (al di fuori dei proprietari del sito, ovviamente :smirking_face:) perché accadrebbe senza il consenso e la conoscenza dell’utente, e allo stesso tempo i dati verrebbero spostati altrove.

Certo, è un po’ un’area grigia e, fondamentalmente, ad esempio, Google lo fa.

Ma… perché? Limita l’accesso solo a Discourse SSO sul lato WordPress e reindirizza gli utenti a Discourse per la creazione dell’account e basta. Ma per quanto ne so, non puoi sincronizzare automaticamente gli account utente fuori dalla scatola. E perché dovresti, dato che con SSO avviene quando un utente ne ha bisogno.

Nel nostro scenario (come organizzazione associativa)

  • Wordpress viene utilizzato per gestire gli abbonamenti, acquistare articoli sul negozio Wordpress e utilizziamo i Gruppi Utente per gestire ciò che un membro può fare nell’organizzazione
  • Discourse è il nostro forum/comunità online e utilizziamo i Gruppi per controllare a quali aree di Discourse un utente ha accesso)

Attualmente un nuovo membro deve creare un account Wordpress (e impostare il proprio abbonamento, ecc.) e anche creare un account Discourse, e i Gruppi Utente-Gruppi Discourse sono gestiti/sincronizzati manualmente.

Sto cercando una soluzione in cui un nuovo utente effettui una sola configurazione per creare entrambi gli account e i Gruppi Utente-Gruppi Discourse vengano sincronizzati automaticamente - sono sicuro che riuscirò a risolvere la sincronizzazione dei Gruppi con le API, ecc. È la configurazione multi-account utente che sto cercando di risolvere/evitare.

Sembra che tu stia usando Discourse come provider SSO per WordPress. Questo approccio è descritto qui: Usa Discourse come identity provider (SSO, DiscourseConnect). Il plugin Discourse per WordPress ha opzioni sia per usare WordPress come provider SSO per Discourse, sia per usare Discourse come identity provider per WordPress. Usare lo stesso nome per entrambi gli approcci crea un po’ di confusione.

Sarei tentato di usare WordPress come identity provider per questo caso. Con questo approccio, gli utenti creeranno account sul tuo sito WordPress e poi accederanno a Discourse con le loro credenziali WordPress. Una cosa da tenere presente con questo approccio è che significa che gli utenti potranno accedere a Discourse solo tramite WordPress, non sarà possibile creare un account Discourse senza avere già un account WordPress. Penso che questa sia la configurazione appropriata quando si integra Discourse con un sito di appartenenza WordPress.

Quando WordPress viene utilizzato come identity provider per Discourse, ci sono un paio di funzioni di utilità utili per impostare le appartenenze ai gruppi di Discourse degli utenti in base alla loro attività su WordPress. Queste funzioni sono descritte qui: Gestisci l’appartenenza ai gruppi in Discourse con WP Discourse SSO.

Tornando alla tua domanda originale:

È passato un po’ di tempo da quando ho esaminato il codice del client DiscourseConnect del plugin WordPress, ma penso che ciò che stai chiedendo sia più o meno il modo in cui quel codice è previsto per funzionare. Se un utente ha un account Discourse, deve solo fare clic sul link “Accedi tramite Discourse” su WordPress e verrà creato un account per lui.

Questo sarebbe tecnicamente possibile quando si utilizza WordPress come client DiscourseConnect, ma a meno che qualcosa non sia cambiato, non sarai in grado di utilizzare i metodi add_user_to_discourse_group e remove_user_from_discourse_group che sono descritti nella documentazione che ho collegato. Dovresti fare qualcosa come impostare un Webhook di Discourse che viene attivato quando un utente viene aggiunto a un gruppo di Discourse, quindi aggiungere del codice su WordPress per elaborare quel webhook. Per sincronizzare i gruppi da WordPress a Discourse, dovresti effettuare una chiamata API a Discourse per aggiornare i gruppi di un utente quando c’è una modifica su WordPress. Quindi, qualcosa che sarebbe abbastanza facile da realizzare se usi WordPress come provider DiscourseConnect potrebbe essere piuttosto complicato se usi WordPress come client DiscourseConnect.

1 Mi Piace

Tranne se viene utilizzato l’accesso personalizzato, come di solito accade con WooCommerce/membership/LLM che mostrano quel pulsante e forzare l’uso del solo SSO di Discourse come provider non avviene immediatamente e richiede un po’ di lavoro personalizzato.

Ci sono un paio di possibili problemi, uno relativo alla cache e un altro relativo ai reindirizzamenti di accesso aggiunti da alcuni plugin. Chiunque riscontri questi problemi dovrebbe chiedere informazioni nella categoria Support > WordPress. Di solito si risolvono facilmente.

Ho completamente dimenticato di riferire: in effetti funziona esattamente come descritto, solo i gruppi manuali sono interessati.

Ciao @simon,
Penso di aver davvero bisogno di aiuto per la risposta 404 dalla mia POC di questa funzionalità SSO. Sto lavorando su questo problema da un giorno intero, ma non riesco ancora a capire qual è il problema. Sono stato in grado di:

  1. Abilitare discourse_connect
  2. Impostare discourse_connect_url su https://localhost:4200/login
  3. Segreto discourse-connect: 20’s (1) 1111111111111111111 per mantenerlo semplice

Quindi, nella mia pagina di accesso della mia app Angular, ho inviato questa richiesta seguendo il codice:


ma ho ricevuto la risposta 404 non trovata.

Per quanto ho capito, durante l’invio di una richiesta POST all’endpoint admin/users/sync_sso, se l’utente non esisteva in discourse, dovrebbe creare un nuovo utente basato su user_id e email, quindi il risultato restituito dovrebbe essere un oggetto utente e non un oggetto vuoto con codice di stato 404.
Ho anche controllato il log, ma non ha fornito alcuna informazione correlata a questa risposta fallita.

Grazie in anticipo!