DiscourseConnect - Official Single-Sign-On for Discourse (sso)

: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 DiscourseConnect protocol allows you to completely outsource all user registration/login in Discourse.

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: Vk.com login (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 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_discourse_connect by mistake and need to revert to the original state and no longer have access to the admin panel, visit /u/admin-login and follow the instructions.

OR, from server console run:

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

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. 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 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 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 = 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=\n

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

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

Finally browser is redirected to:

http://www.example.com/discourse/sso?sso=bm9uY2U9Y2I2ODI1MWVlZmI1MjExZTU4YzAwZmYxMzk1ZjBjMGI%3D%0A&sig=2828aa29899722b35a2f191d34ef9b3ce695e0e6eeec47deb46d588d70c7cb56

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

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:

138 Likes
Discourse SSO + normal login
SSO login & logout issues
Is there a "log_in" SSO API endpoint?
SSO locked me out of Discourse!
What is the SSO login URL
Sync DiscourseConnect user data with the sync_sso route
With SSO my user still need to hit the login button
How to handle Discourse SSO when the authentication site allows users to change emails?
Login to Discourse with website account details
Customized login auth plugin
SSO Login page not showing up
SSO on Discourse using Atlassian Crowd
About the sso category
How to generate nonce from client-side Javascript
Users who register on my site, register also on Discourse Vise Versa
Automatically assigning users to a group
SSO integration & external profile sync help
Mobile (firebase) SSO authentication
Merging users from different forums
Single Sign-Out?
SSO locked me out of Discourse!
Advantage and disadvantage of enabling SSO
Discourse Ruby API testing "Unknown attribute 'auth_token' for User
Using existing RoR application for user auth / signup instead of discourse
User Fields to validate users
Enable sso for my site
Switching out authentication for a passwordless alternative
SSO and e-mail addresses having a plus sign
Auto-sign-in with the OpenId Connect Plugin and AWS Cognito
A way for admins to edit users' external IDs
Shibboleth / SAML / SSO -- Working Implementation for Higher Ed
Automatic session management with OAuth SSO
"User Log out API" return success in response but user session still alive
Discourse SSO using auth0 via URL
SSO with TownNews CMS
Automatic Table of Contents generation
Questions Regarding Account Authentication Methods
Redirect all users who click on domain.com/signup to a different page
Poll Result Breakdown
Cant update email via API - invalid_access error
Disabling all emails except those registration related?
Can't get avatar overrides to work over SSO
DiscourseConnect for MediaWiki
[PAID] Setup SSO for self-hosted instance
How to create and configure Custom User Fields
Conflicting email addresses, giving admins more power to resolve issues
Which Discourse hosting tier should I choose?
New users via API if allow new unchecked
How to change login settings without being logged in?
Segment Tracking Theme Component
Data explorer query to list the longest "estimated read time" topics?
Create user in discourse by redirecting from another site
HMAC-256 example on Official SSO page
Jobs::DownloadAvatarFromUrl times out
Add links to meta.discourse.org instructions inside admin
How to configure the SSO Authorization URL via config (without using the admin panel)
Embedding Discourse Comments via Javascript
TeamAndro exploring a migration from phpBB
DiscourseConnect payload hash encoding mismatch
DiscourseConnect payload hash encoding mismatch
Dev Category sidebar
Disabling SSO in development environment
Getting signed data from the server
User following invitation link is not visible in Admin/Users due to SSO
Getting signed data from the server
Getting signed data from the server
SSO and changing email addresses upstream
Logout POST Request
SSO (maybe) specific issue
SSO (maybe) specific issue
Importing an IPB 3.1 Forum into Discourse
Integration with .NET MVC application for a SaaS platform
Running my own discourse image
Unable to Login to Discourse, using wpdiscourse plugin
Automatic addition of users to group based on email domain
Allowing people to login using accounts from other websites
Seeking Slack Login / SSO for Discourse
Smooth J/K navigation when using keyboard
SSO to Joomla site
Discourse Connect on Local instance is not working
Discourse Connect on Local instance is not working
Implementing SSO for dev environment and troubleshoot
Login with FB, google and apple only
SSO provider implementation - Admin, moderator and groups ignored?
Will Discourse ask for a username if it's not provided to /session/sso_login?
Nextcloud support
How to divide my community into 2 parts
Using Discourse to add a forum feature to our current application?
Shared cookie SSO: Notifying frontend
User auth with website at root and Discourse in subfolder
Can I authenticate to Drupal via Discourse?
Using Discourse as an authentication provider
Difficulty of Tiered-access forum
Disable email verification for SSO
Any way to not require email verification with WP as the SSO Provider?
How to auto-login user in application web view
How can I configure Single Sign On for our App to the Discourse Community Forum
[PAID] automatically change user email
Create user automatically from user creation on our website
Discourse logins into Drupal 8/9
Discourse/Drupal SSO Username Questions
Discourse logins into Drupal 8/9
Open source will support customized provider SSO
Is there a way to get all emails of users with the API?
Show/hide forums based on the domain? (Shared forum via CNAME)
Integrate with DjangoRest and Vue.js
Auto-assign random, anonymous usernames
Connect Discourse Auth with my Django user DB?
Disable account confirm emails when creating users via API
Transforming usernames with SSO
Automatically provision accounts with external SSO provider? (skip Create New Account prompt)
Error ArgumentError in DiscourseSsoController#sso, wrong number of arguments (given 1, expected 0)
Error ArgumentError in DiscourseSsoController#sso, wrong number of arguments (given 1, expected 0)
Embed variables in footer
Create user automatically from user creation on our website
Need help to setup SSO without emails
Programmatically log users out of discourse
How do I remove people from putting names? I have an API system I want in there
How to detect Discourse user on Ghost Blog?
Problem in sso redirection for compose a new pre-filled topic via URL
How to make default avatars and make sure nobody changes there avatar I want to set them an avatar with my API system
How might we better structure #howto?
Is Roblox Login Possible?
Recommended process for disabling DiscourseConnect
Session Timeout SSO after rebuild from Backup
Discourse login with whmcs users
2.7.0.beta4: DiscourseConnect, Topic Timer UI revamp, Login Modal UI revamp, and more
Connecting Discourse invites to Marketo emails
Auto assign member to the certain group
Connecting to an external source of avatars?
Changing avatar_url while sso_overrides_avatar is set?
Disabling email verification
SSO Isnt working for me
Could Discourse offer a StackExchange-like SSO/Federated login service?
Mandatory username & avatar generation - How can we do this?
Intergrading discoures in to a application
How to connect to an external database running on localhost
Automate User Creation
Login Help - Correct way to login
How to install Discourse on an isolated CentOS 7 server
What happens to my current users after configuring SSO?
SSO groups without completely overriding
JumpCloud LDAP/SSO
Lexicon: a customizable native mobile app for your Discourse site
DiscoTOC - automatic table of contents
Hashing Secret + Payload for SSO
Usernames getting modified – numeral “1” being added
Is "partial" SSO possible?
Send an invite to a user but complete their profile programmatically
Magento 2 as SSO Provider?
Discourse SSO Provider doesn't redirect to return_sso_url as user logs in with custom SSO
Force password change after login
SSO and e-mail addresses having a plus sign
Would Discourse meet all of these niche needs to be a video game community forum?
OpenID Connect and SSO
Does `sso overrides groups` work with Oauth2?
How to prevent another log in with Auth0 and OAuth2 Basic Plugin
Add user to group after purchase
Error Log Missing
Shibboleth SSO with Discourse
Using discourse forum in a native app (log-in, languages)
Using Discourse as an identity provider (SSO, DiscourseConnect)
Possible to send an email login link via API?
Possible to send an email login link via API?
Possible to send an email login link via API?
Self serve SSO email synchronisation
Discourse Connect: How implementing Discourse login with an existing database?
Sync group membership with external list of email addresses
Distrust: Discourse as an OpenID Connect provider
Admin sso and group permissions
Discourse Connect: How implementing Discourse login with an existing database?
Can I use my own Login page instead of discourse's Default login dialog box?
2.8.0.beta4: Security Release, New PM Style, and more
Using node.js (discourse-sdk) API when SSO is enabled?
SSO is forcibly creating the user as an admin
DiscourseConnect Won't Let Me Log Back In
DiscourseConnect Won't Let Me Log Back In
DiscourseConnect Won't Let Me Log Back In
DiscourseConnect Won't Let Me Log Back In
DiscourseConnect Won't Let Me Log Back In
Watched words links corrupt @mentions and #categories
Recursive Impersonation
User flair remains after user is removed from group
Recursive Impersonation
Can discourse delete archived posts automatically and accept registration without email?
WP Discourse plugin installation and setup
Disable email verification for SSO
Unable to Test Discourse Connect on localhost
Segment Tracking Theme Component
Using Discourse as an identity provider (SSO, DiscourseConnect)
Drupal 8 and Discourse shared SSO
Admin User API response changed
SSO is forcibly creating the user as an admin
Could you update user avatar for wordpress plugin sso?
Secondary Emails with SSO
Wordpress plugin not redirect to discourse login automatically
Wordpress plugin not redirect to discourse login automatically
Discourse connect sync_sso avatar update issue
DiscourseConnect Single Sign On email & last payload cached across users
Discourse as OpenID Connect provider
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 with Roles translating to Groups
Redirect login possible?
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
PAID: Create Open Source SSO plugin to auth with Wild Apricot
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
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?
User group sync with drupal
Options with SSO with another custom application
Issues in Integrating SSO in Discourse
How to implement Discourse with an already built Rails project
Updating SSO documentation
Configuring SSO to Work With SocialEngine
Updating SSO documentation
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
Custom Login / Registration from another API
SSO and Discourse Consulting
Changing the unique key to identify users
Setting the user title(group?) based on the information that is coming from the sso payload
Mini (Inline) Onebox Support RFC
Error during SSO integration - Wholistic Minds
Advice needed for tailoring Discourse to my organisation
Primary and Discourse Site Integrations
Categorizing and tracking users