Use Discourse as an identity provider (SSO, DiscourseConnect)

It’s only for the case where there are existing users who have unlinked accounts on both WordPress and Discourse. The plugin uses the Discourse user_id to link the accounts. Until they are linked, it needs some way to associate the accounts. The option allows that to be done through the email address. Once the accounts are linked, it isn’t used anymore.

If that option isn’t enabled, the only way for users with existing accounts on both systems to connect them is to click the ‘Link account with Discourse’ link on the user’s profile page.

For accounts that are created through Discourse, the issue never comes up.

The easiest way to see how it works is to try creating separate accounts on WordPress and Discourse using the same username and email, then try logging in through Discourse with the setting disabled. Delete and recreate the WordPress account and try logging in again with the setting enabled. You’ll see what it’s doing.

1 Like

Excellent. We are using WP to serve ads with AWCP, so we have a few users who have created WP accounts already. I asked them to use the same username and email on the WP site, so will this automatically (or Simonmagically) link the two accounts if they’ve used the same username and email?

Yes, if that setting is enabled, the accounts will become linked when they click the ‘Login with Discourse’ link. You can also embed the login link to the WordPress site directly on your forum.

If that setting isn’t enabled, the login process can be a little confusing for users who have existing accounts.

The login process also isn’t great for users who register through WordPress but don’t have an account on the forum - when they complete their registration and go to the WordPress login page, they see the ‘login with Discourse’ link, but it won’t work for them until they create an account on Discourse. Generally, I think the best flow for this feature is to get users to login to the website directly from the forum.

A link with this structure can be embedded on your forum: (I think this can eventually be simplified a bit, we should be able to get rid of the discourse_sso parameter.)

<a href="https://scossar.com/?discourse_sso=1&redirect_to=https://scossar.com/testing-avatar-url/">Login to the Website</a>

If a Discourse user clicks on this link on the forum, it will create an account for them on the website and take them to the ‘testing-avatar-url’ page:

2 Likes

Huge thank you for this, @iamntz. I hit refresh on the PR for weeks until it was merged.

Thanks to @simon for sticking with it as well.

Very nice work and very much appreciated.

Hi, is it possible to pass back & forth more than just the return_sso_url ?

For example if return_sso_url="https://example.com/sso.php" then discourse on successful login redirects to:
https://example.com/sso.php?sso=...&sig=...
but I would like to have:
https://example.com/sso.php?sso=...&sig=...&redirect=/protected_page/42

What is the right way to pass the redirect bit ? as a query parameter ? as a member of the payload data structure ?

1 Like

We have found a workaround by saving the desired additional return url parameter in a cookie on the client

This works for us:

  1. when the user accesses a protected page and we need to check again with the Discourse SSO provider whether she can access that, store the desired return URL in a cookie: setcookie('redirect', $_SERVER['REQUEST_URI'], time()+60, "/", "", true, false);

  2. when the user accesses a protected page, and we let her through, clear the cookie: deleteKey('redirect');

  3. when the SSO is successful and the RETURN_URL gets called by Dicsourse SSO, check if the cookie is set and if so redirect:

if (isset($_COOKIE["redirect"])) {
    $redirect = $_COOKIE["redirect"];
    header("location: $redirect");
} else {
    header("location: $home");
}
3 Likes

Hi, I created a Meteor package to use this protocol, you can find it here:
https://atmospherejs.com/sakerdot/accounts-discourse

Any suggestions are welcome!

5 Likes

Any known module for Drupal which would make a Drupal instance an SSO client?

I would like to dispense with the wordpress login completely and have users register and login using discorse.

What I mean is that currently when a user visits the wordpress site they have to click login and then on the wordpress login page click Log in with Discourse. Is it possible to have wordpress check for a discourse login when the user visits the site and if the user is not logged when clicking the login button re-directed to the discourse login/registration page?

2 Likes

You can switch it so that Discourse is the SSO master so that when people want to log in to WordPress they are redirected to Discourse.

1 Like

I think that could work well, but you should keep the ability for admins to log in directly to the WordPress site. Password protecting the WordPress login form and creating a custom login form that only includes the Discourse login link might work. You can add a Log in with Discourse link to a page with the [discourse_sso_client] shortcode.

1 Like

Looks like something changed there. I had to remove the newline from the returned SSO string which I added intentionally at time of implementation, because otherwise the hash wouldn’t match in my php app. I believe this happened after updating to Discourse 1.9.

Indeed the returned query string contains these fields:

  • admin
  • external_id
  • username
  • groups
  • email
  • nonce
  • name
  • return_sso_url
  • moderator

there is a problem with the groups field though: you’d expect it to be an array, but it actually is a string and it seems to contain only the primary group, except for staff members it is just “staff”.

Is this by design ?

1 Like

OK I decided to troubleshoot this matter myself !
I’ll post my findings here in case that helps others.

First I log onto the docker app, and add this code:

p "====== sso.groups = ", sso.groups

after this line, then I restart unicorn:

sv restart unicorn

when I force the SSO from the webapp, I can find the debug print in log/unicorn.stdout.log, it looks like this:

...
====== sso.groups = ["trust_level_0", "trust_level_1", "trust_level_2", "moderatori", "staff", "Soci"]

whereas in log/production.log I can see the sso parameter passed back to the webapp:

Redirected to https://webapp.example.com/sso.php?sso=bm9uY2U9MzViMmZjYTMwMGQwZWNmZDI5ZWYzNWE0MTIyMGIyMTIyYTczYTU3ZDFmYmEzZjhmMmNmNzAyZTgyZWJjNWViZTE5YmYyYWZkOWUxOGUzZDU1MzFmMWQ0ZjU3MmEwZWE5ZDJkYjM1Y...

when I decode that base64-encoded string, the groups parameter is passed correctly:

groups=trust_level_0&groups=trust_level_1&groups=trust_level_2&groups=moderatori&groups=staff&groups=Soci

and the values appear in the same order as in the ruby debug print above.

So it’s clear that Discourse is sending the groups array correctly !

The trouble must be on the receiving end; we are using a modified form of the community contributed PHP script.

Turns out this code:

$sso = $_GET['sso'];
$sso = urldecode($sso);
$query = [];
parse_str(base64_decode($sso), $query);

is not parsing the multiple groups fields as an array, rather it picks up the last value.

This is a known problem with PHP.

A quick fix is to make the query string compliant to what PHP expects, like this:

$query_string = str_replace('groups', 'groups[]', base64_decode($sso));
parse_str($query_string, $query);
4 Likes

Due to a recent update the workaround above is broke.
Now when I decode that base64-encoded string, the groups parameter is passed like this:

groups=trust_level_0%2Ctrust_level_1%2Ctrust_level_2%2Ctrust_level_3%2Ctrust_level_4%2Cmoderatori%2Cstaff%2Camministratori%2CSoci

The new workaround is:

$query_string = base64_decode($sso);
parse_str($query_string, $query);
$query['groups'] = explode(',', $query['groups']);

Can you add support for letter avatars ie. when a user doesn’t have an avatar, I can’t seem to get that image URL over SSO.

I am using discourse SSO as the login for a PHP project with @paxmanchris code.

/letter_avatar_proxy/v2/letter/v/a3d4f5/120.png 

I can’t seem to figure out how to logout using PHP either…
Figured it out

Just noticed the

[avatar_url]

Has is returning an extra /uploads/default/

/uploads/default//uploads/default/original/2X/9/

[avatar_url]

Is still returning an extra /uploads/default/ in the Avatar URL

/uploads/default//uploads/default/original/2X/9/

If you can tell me which file deals with that I can take a look?

Added a mention that the hex string has to be lower case. Discourse compares the signature string with a lowercase hex signature; it does not decode the hex string and compare it with another decoded hex string.

1 Like

Hello!

We are using Discourse as an SSO provider and we have the need to retrieve user custom fields as part of the payload we get from Discourse.

Is there a way to do this?

Thanks!