Sync SSO user data with the sync_sso route


(Simon Cossar) #1

Single Sign On can be used to handle Discourse user authentication from a separate site. The Official Single Sign On for Discourse topic has details about how to implement SSO.

The Problem

With SSO, Discourse users will be created or updated when they login to Discourse from your external website. What it doesn’t handle is when you need to create or update Discourse users without having them login to your site. For sites that are using SSO, these cases should be handled by making an authenticated POST request to the sync_sso route.

Note: if you are using the Discourse API gem, you can use the gem’s sync_sso method instead of using the following code. See the examples directory for instructions on how to use the method.

As an example, we’ll take a case where a user is added to a group on the parent site, and they need to be added to a corresponding group on Discourse without having to first login with SSO. The name of the group on both the website and the forum is ‘eurorack’. The external_id of the user is 1 and their email is The following code is using PHP. The basic idea can be applied to any programming language.

Setup your API credentials and SSO secret key

$api_key = '4fe83002bb5fba8c9a61a65e5b4b0a3cf8233b0e4ccafc85ebd6607abab4651a';
$api_username = 'system';
$sso_secret = 'jdhb19*Xh3!nu(#k';

Setup the SSO parameters

To see what parameters are available, have a look at the ACCESSORS section of single_sign_on.rb. The parameter that you must include to update an existing user is external_id. If you are calling sync_sso for a user who does not yet exist on Discourse, you must include the username and email parameters. Discourse will use the username and email to create a new user.

To add a user to a group, include the add_groups parameter. To remove a user from a group, include the remove_groups parameter. The value for either of these parameters needs to be set to a comma separated string of group names. Spaces are not allowed between the group names.

The require_activation parameter is being included in the payload. This should be set to true if the user’s email hasn’t been validated on the parent site. With PHP the parameter needs to be set to the string ‘true’ to avoid it being converted to the number 1. If you have validated the user’s email address, you do not need to include this parameter.

// Create an array of SSO parameters.
$sso_params = array(
    'external_id' => 1,
    'email' => '',
    'username' => 'bob',
    'add_groups' => 'eurorack',
    'require_activation' => 'true',

// Convert the SSO parameters into the SSO payload and generate the SSO signature.
$sso_payload = base64_encode( http_build_query( $sso_params ) );
$sig = hash_hmac( 'sha256', $sso_payload, $sso_secret );

Send the POST request

For this example I’ll use curl, set the user_agent to ‘WordPress/4.9.4’, and the forum URL to

$url = '';
$post_fields = array(
    'sso' => $sso_payload,
    'sig' => $sig,
    'api_key' => $api_key,
    'api_username' => $api_username,

$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_POST, 1 );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $post_fields ) );
curl_setopt( $ch, CURLOPT_USERAGENT, 'WordPress/4.9.4' );

$result = curl_exec( $ch );

if ( curl_errno( $ch ) !== 0 ) {
    // Handle error, call curl_close( $ch ) and return.

curl_close( $ch );

$discourse_user = json_decode( $result );

Further Reading

To see what is going on, have a look at the sync_sso code, the SingleSignOn parse method, and the DiscourseSingleSignOn lookup_or_create_user method.

/admin/users/sync_sso ... Route not found
SSO_SYNC not working
Using the API to create a user on an SSO only system
SSO_SYNC not working
Letting users choose whether to show Full Name
/admin/users/sync_sso 403 Forbidden
Triggering automatic authentication via SSO when linking to private topics?
Creating Discourse accounts during user import

I tried using this method to change the email of a user but it just creates a new user with that email. I’ve got SSO overrides email activated and the external_ids match. Is this not what the method is for? Do I need to include their username as well?

Edit: Including the username still creates a new user, just with a number on the end.

Edit2: We realised the issue. We were trying to do this on accounts that had never been signed in through SSO, so of course the external ID wasn’t set. We realised that we should be able to call this endpoint twice, once with the current email address to set up the external id linking and again with the new email address . However if we pass this endpoint the user’s current email and the external ID it says there’s an error validating the signature. If this is changed to their new email address it validates correctly but creates a new user. The signature generator we’re using is exactly the same code that generates the signatures for logging in which works fine.

(Simon Cossar) #3

It seems likely there is an error in your code. What is the error message that you are seeing?

RuntimeError (Bad signature for payload 

sso: redacted

sig: aa41d7cc843337ccff26be9fb2299aeeac09dc2bb754af715ecac589527916e0

expected sig: 3c5dc6ff95dd609aba18c64c32314c2560dcfa1ebe1b9c390ee6538dae7e29a2)

However this only happens when the user’s current email is specified. If a new email is specified the signature validates, although it creates a new user as explained above.

(Simon Cossar) #5

The error message that you are getting is from here:

When Discourse is calculating the payloads signature of the payload, it isn’t matching the signature that you are sending in the sig parameter. I’m not sure why this would only be happening when the user’s current email is specified. You could check your code to see if you are dealing with things differently for this case.

The error is happening before Discourse has done anything with the payload’s data.

(Daniel) #6

Is it possible to create the necessary params $sso & $sig for SSO within a php script, so without the user clicking login in the forum first to receive them via post?

Thanks for your support :slight_smile:

Official Single-Sign-On for Discourse (sso)
(Simon Cossar) #7

I’ve moved your post to this topic. I think that the sync_sso route will do what you’re looking for.

(shahid) #8

Hi, so i need some input for the following scenario.

1- I am migrating users to discourse from another product.
2- These users will login to discourse via the SSO route.
3- But before they login to discourse via SSO, they may change their email address using the web application. In this case, their email in use will be different to the one they were migrated into discourse with.

So when this user SSO’s for the first time, after changing their email address, how do we link them up to their migrated data in discourse?
Originally, I thought of adding a user_id (guid) to identify the customer in discourse as the external_id, but that would only work if they SSO’d in before changing their email address so the SSO record is created with old email/externalId.

Hope my question is clear?

(Simon Cossar) #9

You could try migrating the users to Discourse by calling sync_sso from your external application. That would create Discourse users with an SSO record. If they then login with a different email address, the email address can be updated based on their external_id.

(shahid) #10

That would work if we did the migration via the sync_sso, as you suggested.

In our case, however, the data being migrated is complicated and I am unlikely to be able to influence the migration approach, hence its more than likely it will be migrated and all relevant tables except the single_sigon_records will be populated.

So I am trying to see what I can do from this as my starting point i.e. data already migrated.

So thats why i was wondering if its even possible to populate the single_sigon_records table without going through the sync_sso endpoint?

If i was to run the sync_sso after the data has been migrated, supplying the external_id of choice and the email address, would the SSO record link to the correct user (Provided the email hasnt changed)?

In the case of email changing, its game over and a new user would be created i guess.

(Simon Cossar) #11

If the email address matches the email address of the imported user it will create an SSO record for that user. If the email address has been changed, it will create a new user.

During SSO login Discourse first tries to match the user by the external_id, if that fails it tries to find a user with a matching email address, if that fails, it creates a new user.

(shahid) #12

ok that is clear now.
So as long as the imported user’s email address has not changed:

  1. a sync_sso will utilise the existing sso record for a user who has already sso’d, using external id.

  2. Or create a new SSO record for a first time sso user.

In both cases, as long as the email address has not changed, it will link back the correct user.

So i guess my only route is the sync_sso.
Is adding to the single_signon_records table directly restricted?

(Blake Erickson) #13

It’s not that it’s restricted, its just that feature doesn’t exist and you can only update a certain number of fields via the sync_sso route.

If you wanted to

you would have to write a custom plugin that created a rails migration for the fields you wanted populate.

(shahid) #14

ok, that answers my questions, much appreciated, thanks very much guys.