Configuração DiscourseConnect - Sistema oficial de Single-Sign-On 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 curtidas

Olá, é difícil para mim encontrar em qualquer lugar, mas qual protocolo isso usa? Assumir oauth2? Os parâmetros não parecem corresponder, e recebo um erro do provedor de que ele parece estar aceitando um parâmetro sso=? Ajuda!

Obrigado

DiscourseConnect é a implementação de SSO do Discourse. Ele não usa um protocolo padrão.

Se você não se importar em olhar o código PHP, há um exemplo de implementação aqui: wp-discourse/lib/sso-provider/discourse-sso.php at main · discourse/wp-discourse · GitHub.

Sim, isso não vai funcionar. Se você tiver um provedor OAuth2 que gostaria de usar para autenticar usuários, dê uma olhada no plugin Discourse OAuth2 Basic.

1 curtida

Obrigado @simon. O código PHP é um provedor também, e não um consumidor? Também vi um provedor OIDC que pode funcionar e também um provedor “intermediário” na área de plugins.

Se o provedor SSO não for padrão, para quem/o que ele se destina se não funcionar bem com outros?

Obrigado novamente!

O código que eu linkei é para usar o WordPress como provedor de autenticação para o Discourse.

O plugin do WordPress também permite que o WordPress seja usado como um cliente DiscourseConnect: wp-discourse/lib/sso-client at main · discourse/wp-discourse · GitHub.

Não tenho certeza qual foi a motivação para adicionar uma implementação SSO personalizada ao Discourse. Imagino que houve um caso de negócio para isso.

Um benefício que ele fornece é que permite que um site externo seja rigidamente integrado ao Discourse. Por exemplo, todos os atributos de usuário listados aqui podem ser sincronizados com o Discourse durante o processo de autenticação: discourse/lib/discourse_connect_base.rb at 7b89fdead98606d4f47ceb0a1d240d0f6e5f589e · discourse/discourse · GitHub.

Ele também permite que sites que não estão configurados para serem provedores OAuth2 ou OpenID Connect sejam usados para autenticar usuários no Discourse.

A desvantagem é que exige a adição de algum código personalizado no site do provedor de autenticação.

1 curtida

Olá, estou curioso sobre quais são os problemas de não verificar endereços de e-mail em um site externo que fornece SSO. É apenas que isso permite spam automatizado? Ou existem outras considerações? Por que não é recomendado que o Discourse lide com a verificação de e-mail se o site externo não o fizer?

Obrigado por qualquer insight adicional.

O pior cenário que conheço requer estas condições:

  • os endereços de e-mail não são verificados no site externo
  • require_activation=true não está definido no payload do SSO
  • existem contas existentes no site Discourse que não têm um SingleSignOnRecord associado a elas (o proprietário da conta nunca fez login no Discourse com SSO)

Nesse caso, alguém poderia se inscrever no site externo usando o endereço de e-mail de um usuário do Discourse que nunca fez login com SSO. Isso permitiria que a conta não verificada do site externo assumisse a conta do Discourse que usa o mesmo endereço de e-mail. Isso seria especialmente preocupante se fosse uma conta de administrador no Discourse.

É recomendado que o Discourse lide com a verificação de e-mail se o site externo não estiver lidando com isso:

Existem algumas razões pelas quais é melhor lidar com a verificação de e-mail no site externo:

  • forçar os usuários a receber o e-mail de confirmação do Discourse adiciona algum atrito quando os usuários tentam fazer login no Discourse pela primeira vez. (Realisticamente, esse atrito tem que acontecer em algum lugar - seja no lado do Discourse ou no lado do site externo.)
  • O Discourse não corresponderá às contas existentes do Discourse com logins externos com base no endereço de e-mail se require_activation for definido como true no payload do SSO. Isso é um problema se você habilitar o DiscourseConnect depois que algumas contas foram criadas no Discourse registrando-se com nome de usuário/senha. Também é um problema se, por qualquer motivo, você precisar excluir entradas SingleSignOnRecord no Discourse. O Discourse não criará automaticamente novas entradas SingleSignOnRecord quando os usuários tentarem fazer login novamente no Discourse.
4 curtidas

Obrigado, @simon - isso é muito útil!

Olá, tenho uma dúvida sobre o campo groups na carga útil do SSO.

Grupos automatizados (como administradores, moderadores e níveis de confiança) também serão substituídos? Ou serão mantidos?

Não! Assumindo que a descrição dessa configuração esteja correta, ela afetará apenas grupos manuais.

Sabe de uma coisa… eu não vi a palavra “manual” na descrição. Parece promissor para o meu caso de uso, vou tentar e reportar.

1 curtida

Quando li isto, pensei que deveria gerar a assinatura diretamente da carga útil codificada em base64. Não percebi que é preciso gerá-la a partir dos bytes UTF-8. Poderia isto ser esclarecido?

Tenho brincado com o DiscourseConnect - a documentação é ótima, obrigado.
No entanto, encontrei alguns obstáculos sobre os quais espero obter ajuda/esclarecimento.

Queremos que os usuários possam fazer login no WordPress usando seu login do Discourse (isso está funcionando bem :slight_smile: ) - no entanto.

  • É possível criar um usuário do WordPress via Cadastro no Discourse (quando um usuário cria uma conta no Discourse, ela cria automaticamente uma conta/perfil no WordPress para que eles possam fazer login no WordPress)?

  • É possível sincronizar Grupos de Usuários do WordPress e Grupos do Discourse?
    Se um usuário tiver uma conta de usuário tanto no WordPress quanto no Discourse, o DiscourseConnect é capaz de uni-las - mas não dá à conta do WordPress os Grupos de Usuários dos Grupos do Discourse com o mesmo nome, e vice-versa (e o que acontece se os grupos não tiverem o mesmo nome - como posso dizer ao DiscourseConnect para dar o Grupo de Usuário ‘Grupo de Testes’ quando o usuário tem o Grupo do Discourse ‘Grupo para Testar Coisas’?

O que estou perdendo?

Eu não sei a resposta, mas…

Tenha cuidado com isso. É algo ilegal e amplamente considerado má prática (fora os proprietários do site, é claro :smirking_face:), pois aconteceria sem o consentimento e conhecimento do usuário, e ao mesmo tempo os dados seriam movidos para outro lugar.

Claro, isso está em uma área cinzenta e, basicamente, por exemplo, o Google está fazendo isso.

Mas… por quê? Limite o login apenas ao Discourse SSO no lado do WordPress e redirecione os usuários para o Discourse para criação de conta e pronto. Mas, tanto quanto sei, você não pode sincronizar contas de usuário automaticamente. E por que deveria, já que com o SSO isso acontece quando um usuário precisa.

Em nosso cenário (como uma organização de membros)

  • O WordPress é usado para gerenciar assinaturas, comprar itens na loja do WordPress e usamos Grupos de Usuários para gerenciar o que um membro pode fazer na organização
  • O Discourse é nosso fórum/comunidade online e usamos Grupos para controlar a quais áreas do Discourse um usuário tem acesso)

Atualmente, um novo membro precisa configurar uma Conta do WordPress (e configurar sua assinatura, etc.) e também configurar uma conta no Discourse, e os Grupos de Usuários-Grupos do Discourse são gerenciados/sincronizados manualmente.

Estou tentando encontrar uma solução onde um novo usuário faça uma única configuração para criar ambas as contas, e os Grupos de Usuários-Grupos do Discourse sejam sincronizados automaticamente - tenho certeza de que posso resolver a sincronização de grupos com APIs, etc. É a configuração de conta de usuário múltipla que estou tentando resolver/evitar.

Parece que o que você está fazendo é usar o Discourse como provedor de SSO para o WordPress. Essa abordagem está descrita aqui: Use o Discourse como um provedor de identidade (SSO, DiscourseConnect). O plugin Discourse WordPress tem opções para usar o WordPress como provedor de SSO para o Discourse, ou para usar o Discourse como um provedor de identidade para o WordPress. Usar o mesmo nome para ambas as abordagens leva a alguma confusão.

Eu ficaria tentado a usar o WordPress como o provedor de identidade para este caso. Com essa abordagem, os usuários criarão contas em seu site WordPress e, em seguida, farão login no Discourse com suas credenciais do WordPress. Uma coisa a ter em mente com essa abordagem é que isso significa que os usuários só poderão fazer login no Discourse através do WordPress, não será possível criar uma conta no Discourse sem já ter uma conta no WordPress. Acho que essa é a configuração apropriada ao integrar o Discourse com um site de membros do WordPress.

Quando o WordPress é usado como provedor de identidade para o Discourse, existem algumas funções utilitárias que são úteis para definir as associações de grupo do Discourse de um usuário com base em sua atividade no WordPress. Essas funções estão descritas aqui: Gerenciar associação de grupo no Discourse com WP Discourse SSO.

Voltando à sua pergunta original:

Faz um tempo que não olho o código do Cliente DiscourseConnect do plugin WordPress, mas acho que o que você está pedindo é mais ou menos como esse código é esperado funcionar. Se um usuário tiver uma conta no Discourse, ele só precisa clicar no link “Fazer login através do Discourse” no WordPress e uma conta será criada para ele.

Isso seria tecnicamente possível ao usar o WordPress como Cliente DiscourseConnect, mas a menos que algo tenha mudado, você não poderá usar os métodos add_user_to_discourse_group e remove_user_from_discourse_group que estão descritos na documentação que linkei. Você precisaria fazer algo como configurar um Webhook do Discourse que fosse acionado quando um usuário fosse adicionado a um grupo do Discourse, então adicionar algum código no WordPress para processar esse webhook. Para sincronizar grupos do WordPress para o Discourse, você precisaria fazer uma chamada de API para o Discourse para atualizar os grupos de um usuário quando houvesse uma mudança no WordPress. Portanto, algo que seria bastante fácil de realizar se você usar o WordPress como provedor do DiscourseConnect pode ser um tanto complicado se você usar o WordPress como cliente do DiscourseConnect.

1 curtida

Exceto se o login personalizado for usado, como geralmente é a situação com WooCommerce/memberships/LLM mostrando esse botão e forçando o uso apenas do Discourse SSO como provedor não está acontecendo pronto para uso e precisa de algum trabalho personalizado.

Existem alguns problemas possíveis, um relacionado ao cache e outro relacionado a redirecionamentos de login que são adicionados por alguns plugins. Qualquer pessoa que encontrar esses problemas deve perguntar sobre eles na categoria Support > WordPress. Eles geralmente são facilmente resolvidos.

Esqueci totalmente de dar retorno: de fato funciona exatamente como descrito, apenas grupos manuais são afetados.

Olá @simon,
Acho que realmente preciso de ajuda com a resposta 404 da minha prova de conceito (POC) deste recurso SSO. Tenho trabalhado neste problema o dia todo, mas ainda não consigo descobrir qual é o problema. Consegui:

  1. Habilitar o discourse_connect
  2. Definir o discourse_connect_url para https://localhost:4200/login
  3. Segredo do discourse-connect: 20’s (1) 1111111111111111111 para simplificar

Então, na minha página de login do meu aplicativo Angular, enviei esta solicitação seguindo o código:


mas recebi a resposta 404 não encontrada.

Pelo que entendi, ao enviar uma solicitação POST para o endpoint admin/users/sync_sso, se o usuário não existisse no Discourse, ele deveria criar um novo usuário com base no user_id e email, então o resultado retornado deveria ser um objeto de usuário, não um objeto vazio com código de status 404.
Também verifiquei o log e ele não forneceu nenhuma informação relacionada a essa resposta falha.

Obrigado antecipadamente!