Using the Discourse SSO provider feature


(Paxmanchris) #1

I am writing php code to authenticate a user using the Discouse SSO provider feature. I enabled the enable sso provider option and set a sso secret in the login settings. I found the help to use this option very sparse and spread out throughout the meta. A post by @ntauthority proved useful. So Here is my code so far:

<?php


 class GenericSSO {
    
    public function __construct($apiEndpoint) {
        $this->apiEndpoint = $apiEndpoint;
        $this->headers = array();
    }
    
    public function call($method, $action,$params) {
        $url = $this->apiEndpoint.$action;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        if($method=="POST"){
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params) );
        }
        elseif ($method == "GET") {
            curl_setopt($ch, CURLOPT_URL, $url.'?'.http_build_query($params));
            curl_setopt($ch, CURLOPT_HTTPHEADER , $this->headers );
            //
        }
        else {
            die("Method must be POST or GET");
        }
        
        $exec = curl_exec($ch);
        $info = curl_getinfo($ch);

        //print_r($exec);
        if($info["http_code"] == 200) {
            curl_close($ch);
            return json_decode($exec,true);
        } else {
            die("An error happened:<pre>".curl_error($ch));
        }
    }

    public function add_header($name, $value){
        array_push($this->headers, "$name: $value");
    }
    
}


$api = new GenericSSO("http://discouse.example.com");

$results =  $api->call('GET', '/session/csrf.json', array());

$csrf = $results['csrf'];

# not working
/*
$params = array(
    'login' => 'username',
    'password' => 'thepassword'
);
$api->add_header("X-CSRF-Token", $csrf);
//*/

# working
/*
$api_key = 'theapikeyasdfjiJIDFJISDJFISDJFISJDFISJDFIJSDF';
$api_user = 'bob';
$params = array(
    'login' => 'username',
    'password' => 'thepassword',
    'api_key' => $api_key,
    'api_username' => $api_user
);
//*/

$api->add_header("Content-Type", "application/x-www-form-urlencoded");

$results = $api->call('POST', '/session.json', $params);

print "<pre>results:\n";
print_r($results);

This will get the csrf without issue. I then expected that it would just need to pass the login and password, with the csrf header. This is the “# not working” section of the code. But, if I include the api_key and api_user, it will login the user, with or without the csrf and will fail if the password is wrong (this is the “# working” part).

Here is the weird, part, the former seems to work if I use the postman chrome extension. First I get the csrf (note, I replaces real url and keys with example ones):

Then I make a post to session.conf with the csrf header, it work:

and if the csrf is bad:

and if I include the api_key with no header, it works:

Any advice on how to write the code would great.


(Paxmanchris) #2

Ignore my last post

So I figured out my issue in the last post. Postman is able to retain the browser cookies, and is able to use that to complete the login action. I ended
up scrapping this idea and starting from scratch.

Using session/sso_provider

After reading this post very carefully, I was able to write php code to do some sso loign.


this will render a page with a link, 'sign in with Discourse". I wanted to use $_SESSION to store the login information in this example, but when Discourse makes the ajax call back, the session variable does not get set. So I create a function that used a database instead.

When logged off of Discourse

  • The link will redirect you to the Discourse site, and the login window will pop up
  • When you enter in a valid username and password, Discourse makes a call in the background to the script, or what ever you define as return_sso_url, with sso and sig set in the query string. As long as you return with header("Access-Control-Allow-Origin: *");, Discourse will assume it is successful.
  • Discourse will then redirect you index of your Discourse site, I would assume it would redirect you back to the orgin site.

When already logged on Discourse

  • In this case Discourse will redirect you back to your script

What to do next

My goal here, is to authenticate a user on my site using Discourse as an authority. I want the user to be redirected back to my site after the user has logged on with Discourse.


How to use the Discourse auth system for another app?
(Paxmanchris) #3

I would appreciate some response from the Developers on this. At least give me some clue, where in this code this redirect is happing.


(Sam Saffron) #4

This is already supported by the protocol just set return_sso_url in your payload and we will redirect you back after being logged in.


Login redirect during sso provider login
(Paxmanchris) #5

If the user is not logged on in Discourse, the user is first, directed to the /login screen after clicking the sso link. After successful login there is a ajax call in the background to return_sso_url (not redirected) with the sso and sig url parameters set, and I assume this is where I preform the logic to login the user on my site . then user is redirected to Discourse site main page /.

If you click the link, and the user is logged on to Discourse, the user will get redirected Which is what I want the first time around.

I made a screen cast of this:


(Sam Saffron) #6

Yeah, that is a bug, we should fix it


(Sandeep Dange) #7

From where to enable sso provider option and set a sso secret? I don’t find option of login settings? Can someone point me on this?


(Paxmanchris) #8

Goto your Admin -> Settings -> Login ->

and look for, sso url, sso secret, and enable sso provider

I am not sure what sso url is supposed to be, I just put in the SSO consumer site. Check off enable sso provider, and fill in sso secret with random stuff.


(Sekhat) #9

for using the provider, I have discovered today, while working at getting it working the following.

In the admin panel, only the enable sso provider and sso secret are required for using discourse as an sso provider.

From your site that wishes to use a discourse instance as an sso provider you’ll do the following:

  • generate a random nonce, somehow keep track of these so you can tell if a returned nonce is valid. Follow discourse’s example of making these time limited.
  • create a payload which should look like the following
    nonce=NONCE&return_sso_url=RETURN_URL
  • You’ll probably want to make sure both NONCE and RETURN_URL are url encoded as the payload is supposed to be an embedded query string
  • base64 encode the payload with standard base64 encoding (We’ll call this PLB64)
  • url encode PLB64 (we’ll call this PLUE)
  • Generate a HMAC+SHA256 signiture from PLB64 using your sso_secret as the key, then create a hex string from this (We’ll call this HEXSIG)
  • Redirect the user to DISCOURSE_ROOT_URL/session/sso_provider?sso=PLUE&sig=HEXSIG

Providing you’ve done this right, discourse should redirect to to your RETURN_URL.

You’ll have to query string paramaeters sso and sig with these you’ll want to do the following:
(assuming your web framework of choice already URL decodes the values)

  • compute the HMAC_SHA256 of sso again using sso_secret as your key.
  • convert sig from it’s hex string representation back into bytes.
  • compare the two values to make sure they are equal. Ideally using some none time leaking comparison function.
  • Then, if you Base64 Decode sso, you’ll get the passed embedded query string.
  • This will have a key called nonce which value should match the nonce passed originally. Check that this is the case.
  • You’ll find this query string will also contain a bunch of user information, use as you see fit.

Hope this helps. I’ve written a go package that handles this, so you can read this if you wish to see the process in code.

(I didn’t read through the post, it seems when I was initially looking into this today I found a few posts asking about this, but hadn’t got quite this far and assumed this to be one of those posts, so sorry, this post is probably better suited else where)


(Love Chopra ) #10

@paxmanchris are you able to redirect user to return_sso_url after login ? I also want to have the same behaviour, please let me know if you have succeed with this?

@sam please suggest is this bug fixed?


(Paxmanchris) #11

A bug report was made. So eventuallyTM it will get fixed.


(Arpit Jalan) #12

Thanks for the detailed steps @sekhat! I used much of this info to create a detailed howto topic:

Also added a link of your Go package so that other people can use that as reference implementation :smile:


(Arpit Jalan) #13

Fixed :turtle:

https://github.com/discourse/discourse/commit/c28843e87bb8d4fc94124a4d530acc988d60e579


(Erlend Sogge Heggen) #14