Official Single-Sign-On for Discourse (sso)

Discourse now ships with official hooks to perform auth offsite.

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 SSO is to replace Discourse authentication, if you would like to add a new provider see existing plugins such as: login (vkontakte)

Enabling SSO

To enable single sign on you have 3 settings you need to fill out:

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

Once enable_sso is set to true:

  • Clicking on login or avatar will, redirect you to /session/sso which in turn will redirect users to sso_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?

If you check enable_sso by mistake and need to revert to the original state and no longer have access to the admin panel, visit /users/admin-login and follow the instructions.

OR, from server console run:

cd /var/discourse
./launcher enter app
rails c
irb > SiteSetting.enable_sso = false
irb > SiteSetting.enable_local_logins = true
irb > exit

Implementing SSO 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 sso_url with a signed payload: (say sso_url is

You will receive incoming traffic with the following

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

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

nonce=ABCD, this raw payload is base 64 encoded.

The endpoint being called must

  1. Validate the signature: ensure that HMAC-SHA256 of sso_secret, PAYLOAD is equal to the sig (sig will be hex encoded).
  2. Perform whatever authentication it has to
  3. Create a new 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.sso_overrides_username is set.
    • name will become the full name on Discourse if the user is new or SiteSetting.sso_overrides_name is set.
    • avatar_url will be downloaded and set as the user’s avatar if the user is new or SiteSetting.sso_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.sso_overrides_bio is set.
    • Additional boolean (“true” or “false”) fields are: admin, moderator, suppress_welcome_message
  4. Base64 encode the payload
  5. Calculate a HMAC-SHA256 hash of the payload using sso_secret as the key and Base64 encoded payload as text
  6. Redirect back to 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.

Specifying group membership

If the sso 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 sso 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 = SingleSignOn.parse(request.query_string, secret) = "" = "Bill Hicks"
    sso.username = ""
    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")

Transitioning to and from single sign on.

The system always trusts emails provided by the single sign on endpoint. This means that if you had an existing account in the past on Discourse with SSO disabled, SSO will simply re-use it and avoid creating a new account.

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

Real world example:

Given the following settings:

Discourse domain:
SSO url :
SSO 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=\n

  • Payload is URL encoded: bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D%0A

  • HMAC-SHA256 is generated on the Base64 encoded Payload: 2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56

Finally browser is redirected to:

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
username: samsam
require_activation: true

Unsigned payload is generated:


order does not matter, values are URL encoded

Payload is Base64 encoded


Payload is URL encoded


Base64 encoded Payload is signed


Browser redirects to:

Synchronizing SSO records

You can use the POST admin endpoint /admin/users/sync_sso to synchronize an SSO record, pass it the same record you would pass to the SSO 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 as url parameters

Clearing SSO records

If your external_id values from your SSO 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:


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 sso 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.

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.

  • Consider adding a discourse_sso gem to make it easier to implement in Ruby.

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.sso_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

Debugging your SSO provider

To assist in debugging SSO you may enable the site setting verbose_sso_logging. By enabling that site setting rich diagnostics will show up in

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

  • Every time the SSO process is initiated we will log a warning to the log with a full dump of the SSO payload

  • Every time a user fails to complete SSO (due to nonce expiring or ip block)

SSO for dummies
Discourse SSO + normal login
SSO login & logout issues
Do you recommend that I host my Discourse and Wordpress site separately?
SSO locked me out of Discourse!
Automatically assigning users to a group
Is there a "log_in" SSO API endpoint?
SSO integration & external profile sync help
With SSO my user still need to hit the login button
What is the SSO login URL
Advantage and disadvantage of enabling SSO
Switching out authentication for a passwordless alternative
How to handle Discourse SSO when the authentication site allows users to change emails?
SSO and e-mail addresses having a plus sign
Login to Discourse with website account details
Sync SSO user data with the sync_sso route
Users who register on my site, register also on Discourse Vise Versa
About the sso category
SSO locked me out of Discourse!
Merging users from different forums
Using existing RoR application for user auth / signup instead of discourse
Shibboleth / SAML / SSO -- Working Implementation for Higher Ed
Categorizing and tracking users
SingleSignOnRecord.destroy_all - Imported users not usable (User {:primary_email=>"has already been taken"})
User auth with website at root and Discourse in subfolder
Using Discourse to add a forum feature to our current application?
Will Discourse ask for a username if it's not provided to /session/sso_login?
SSO provider implementation - Admin, moderator and groups ignored?
Automatic Table of Contents generation
Updating SSO documentation
Redirect login possible?
Smooth J/K navigation when using keyboard
Implementing SSO for dev environment and troubleshoot
SSO to Joomla site
How to divide my community into 2 parts
Is there a way to get all emails of users with the API?
Can't get avatar overrides to work over SSO
Dev Category sidebar
Can I authenticate to Drupal via Discourse?
Discourse SSO using auth0 via URL
SingleSignOnRecord.destroy_all - Imported users not usable (User {:primary_email=>"has already been taken"})
A way for admins to edit users' external IDs
SSO for dummies
Integration with .NET MVC application for a SaaS platform
Importing an IPB 3.1 Forum into Discourse
SSO (maybe) specific issue
SSO (maybe) specific issue
SSO and changing email addresses upstream
Getting signed data from the server
Getting signed data from the server
Getting signed data from the server
User Fields to validate users
HMAC-256 example on Official SSO page
"User Log out API" return success in response but user session still alive
How do I connect dashboard with Discourse?
Single Sign-Out?
Allowing people to login using accounts from other websites
Unified Experience / Custom Layouts - Recommended Approach
SSO Client avatars
Updating SSO documentation
How to configure the SSO Authorization URL via config (without using the admin panel)
Changing the unique key to identify users
Integrate with DjangoRest and Vue.js
Add links to instructions inside admin
Redirect all users who click on to a different page
Questions Regarding Account Authentication Methods
Difficulty of Tiered-access forum
Polls Breakdown
How to change login settings without being logged in?
Discourse Ruby API testing "Unknown attribute 'auth_token' for User
New users via API if allow new unchecked
SSO with Roles translating to Groups
Login to Discourse with custom Oauth2 provider
Can I log into multiple instances of discourse simultaneously?
Upgraded last night and login button no longer works
SSO Login page not showing up
Disable account confirm emails when creating users via API
How to disable SSO via SSH
Connect Multiple WP Sites To 1 Discourse Installation?
What is the procedure to obtain CAS between my website and my discourse instance?
Problem logging in using SSO plugin
Transforming usernames with SSO
Can I remove the requirement for E-Mail signup?
PAID: Create Open Source SSO plugin to auth with Wild Apricot
[PAID] automatically change user email
Trouble connecting drupal and discourse
About the idea: IDENTITY = EMAIL
About the idea: IDENTITY = EMAIL
Consequences of not validating email addresses
SSO and Restricted Groups
Questions about Discourse on Digital Ocean
Integration Discourse in our app
Require users to join at least one group at sign-up
Change right gutter to vertical timeline + topic controls
Use the same user database and login credentials in multiple discourse instances
How to connect my (existing) User Database?
Options with SSO with another custom application
Issues in Integrating SSO in Discourse
Customized login auth plugin
How to implement Discourse with an already built Rails project
Configuring SSO to Work With SocialEngine
SSO synced login state tips
SSO synced login state tips
Discourse view file update does not reflect in browser
Discourse view file update does not reflect in browser
Trying to set up SSO
Discourse doesn't re-verify an address changed by SSO
Discourse doesn't re-verify an address changed by SSO
Show/hide forums based on the domain? (Shared forum via CNAME)
Automatically provision accounts with external SSO provider? (skip Create New Account prompt)
SSO and Discourse Consulting
Mini (Inline) Onebox Support RFC
How to generate nonce from client-side Javascript
Advice needed for tailoring Discourse to my organisation
Disabling all emails except those registration related?
SSO on Discourse using Atlassian Crowd
Primary and Discourse Site Integrations
Recommended process for disabling SSO (Single-Sign-On)
Disabling SSO in development environment
TeamAndro exploring a migration from phpBB
Setting the user title(group?) based on the information that is coming from the sso payload
Shared cookie SSO: Notifying frontend
Create user in discourse by redirecting from another site
Allow 1. site users to login 2. site
Which Discourse hosting tier should I choose?
Enable sso for my site

What’s the logic behind disabling Discourse’s built-in authentication when SSO is enabled? I’m sure there’s a reason, I’m just having trouble reasoning about it.

For context, here’s what my group would like to do: we have our app running with its own authentication. Our Discourse instance is running elsewhere. We’d like to have users be able to view and participate in the forum without necessarily having an account with our app, but with the possibility that they will eventually have an app account (and then use the app login to access Discourse via the SSO method).


You need to build it as an OAuth provider, then. The idea with SSO is that it’s the single source of authority - for example, people hooking it up to their Active Directory domains. That clearly isn’t what you want, so…

What you can do right now is enable Google/Facebook/Yahoo login (these are all ~3 clicks), then add a “user custom field” for their account name on your service.

I’m wondering how does the SSO code handle this case:

  1. User creates account on sso.example.tld
  2. User signs on to forum.example.tld
  3. User goes to profile on sso.example.tld and changes their email address
  4. User then goes to forum.example.tld

At step 4, will Discourse still be able to figure out that the User from sso.example.tld with a changed email address is the same user in Discourse’s User database that has the user’s old email address stored?
If it can’t and instead creates a new Discourse user for that person because of the changed email address then that would be confusing and problematic for the user and the admin.


I just went through this same research. After the account is created, every time User’s payload is sent to discourse this will happen.

The User is logged into the forum account associated with your external ID, and it will change the email address to User’s new address.

Note that this only happens if sso overrides email is on – otherwise, the address submitted via SSO is only used for the first lookup or when the account is created.


Is there any way to automatically sign them in when they hit the discourse forums, if they are already logged into our system? If do they always have to hit “Login” on the discourse forum first?


If your forum should only be available to SSO users, you can enable the require login site setting, and users will be logged in automatically. If not: I don’t think there is an easy way to do it.


What I’ve done is sending users that are logged in on our platform to the link:

and those that are not logged in to:

Not sure if it’s the right method, but seems to work for us.


Is there a way to disable #3 in this list and make the login fail if the user doesn’t already exist (if external_id or matching email doesn’t match) on the Discourse?

Quick question if anybody has a minute to answer.

If a user is inactivated on the site used for SSO, how does discourse know/handle it?

It doesn’t – it’s the SSO site’s responsibility to handle this. It should prevent new sign-ins and possibly log the user out remotely via the API*.

*It would be awesome if Discourse had an API to log out users using their external_id


Yeah, right now that takes two requests - first is /users/by_external and second is the log out.

1 Like

What if I want to do it a different way round. I want Discourse to register the user, but then I want to pull the data to my home site, for the additional non forum features I provide my customers?

OR authenticate my websites login again Discourse (being the end point)

1 Like

@Guss I think what you want is Using Discourse as a SSO provider

Note, I just added optional rich logging for SSO via:

This site setting verbose_sso_logging can help you debug your SSO provider, however, be careful it gets very loud… only enable while you are debugging issues with your provider.


I created a very simple but working example SSO Endpoint that anyone can use for testing or learning how SSO works:


Bit of an odd case here, I want to use Discourse SSO but my site doesn’t collect emails. Is there a way to send the username and name to Discourse, but then have Discourse collect and validate the email? Otherwise I can just create a page that collects and validates emails before redirecting back to Discourse.

I’m pretty sure “email” is a required field, so you have to pass in a dummy email, and then you can set require_activation to “true”:

I’m not sure what happens at this point though. They might not be able to log in without a valid email first. But if they can login, they can then reset the dummy email to a valid email. But I’m not even sure if that is possible because I think we send an email confirmation to the old email before changing it, but that might be just for admins. Anways, you might just have to try it on a test setup and see what happens.