Dec 30, 2014

Discourse - Single-Sign-On Authentication

blogpost cover image
Do you have your own Rails application? Do you want to build a place for discussion and conversation? Do you have a users database and don’t want to change it? Are you using Devise for user authentication in your app? Do you want to integrate the process with new forum? Don’t worry, a prefect solution is already prepared and available.


If you want to integrate your app with a forum there is no need to build one on your own. Discourseis an open-source Rails discussion platform with a Postgres database and an Ember front-end. It is free, all you need is to clone it. Discourse have many advantages which you can find on its web page. What’s more, setting up your own forum is explained in an easy, step-by-step tutorial given in dicourse docs: Discourse Setup. If you don’t have any other issues (for example a popular problem with Nokogiri gem installation) it will take about half an hour to do it. So where is the problem? Integration of the authentication process is difficult, especially when using Devise. It is a new solution, and still need a little explanation because the content of official Single-Sign-On page is far from perfect.


Here is an example implementation of integration for both applications. To make the single-sign-on enabled in the Discourse application open the site_settings.yml file and search for enable_sso. Set it totrue. In sso_url add the url that you are going to use for login to the action in your appliction. Application password is stored in sso_secret, it will be used for generating authentication codes and will be used on both sides of the process to recognize your application.

    client: true
    default: true
  sso_url: 'http://yourapp/sso'
  sso_secret: 'yoursupersecurepassword'
  sso_overrides_email: false
  sso_overrides_username: false
  sso_overrides_name: false

If you are using Devise in your app, you probably have a session_controller for login action. You need a similar method called sso, and put the code shown below. SSO_SECRET should be the same as one given in Discourse site_settings.ymlFORUM_URL is the url to the action in Discourse you want a user to visit after login.

include UrlHelper
require 'openssl'
require 'base64'

def sso
  # authenticate
  self.resource = warden.authenticate(auth_options)
  resource_name = self.resource_name
  sign_in(resource_name, resource)
  if member_signed_in?
    # redirect to forum
    sig = params[:sig]
    sso = params[:sso]
    if OpenSSL::HMAC.hexdigest('sha256', SSO_SECRET, sso) == sig
      nonce = Base64.decode64(sso)
      sso = Base64.encode64(nonce + '&username=' + resource.nick + '&email=' + + '&external_id=' +
      sig = OpenSSL::HMAC.hexdigest('sha256', SSO_SECRET, sso)
      return_params = { sso: sso, sig: sig }
      redirect_to generate_url( FORUM_URL, return_params )
    redirect_to root_path, alert: t('.sign_in')

The magic is explained in next paragraph, but to make it work you should also add a url_helper with generate_url method.

# helper for creating redirects with urls
module UrlHelper
  def generate_url(url, params = {})
    uri = URI(url)
    uri.query = params.to_query

How it really works?

If everything is working you can finish your task, but if you are curious about what you just did, a short explanation. Generally for signing in, a Discourse application creates an encoded payload and signature. Payload is a number generated using a ruby library for secure number generator – SecureRandom. It is created as a hexadecimal string using SecureRandom.hex and then is Base64 encoded. It has also expiry time defined in SingleSignOn class and equal to 10 minutes. This payload is then used for signature generation. OpenSSL::HMAC.hexdigest('sha256', SSO_SECRET, PAYLOAD) creates a hash-based message authentication code using also the password stored in SSO_SECRET. HMAC is a function used for calculating a message authentication code using hash function (256-bits secure hash algorithm) and secret password. It is very secure, but its strength depends on the size of password. In case you choose a long and random string, and it’s expiry time is 10 minutes long it is very difficult to break. Payload and signature are passed as a parameters to the application, after Discourse ‘login’ button is clicked. For example, if nonce (SecureRandom.hex) is “6e078ed54dd20c0bee38585e32dd04aa” and secure password is “supersecure” the parameters will looks like these:

nonce = "6e078ed54dd20c0bee38585e32dd04aa"
password = "supersecure"
sso (payload) = "NmUwNzhlZDU0ZGQyMGMwYmVlMzg1ODVlMzJkZDA0YWE=\n"
sig (signature) = "37d27924092e4c8305938e4fa3b1822edaa668a852d5d4bc592075ace10ed2a1"

After redirecting to your application authentication action the url will look like this:

Now it is pretty easy to understand what will happen in the sso action. Both parameters (sso and sig) will be used to check if the application is allowed to authenticate users. Received payload is used to generate another HMAC-SHA256 using password stored in the application, and then compared to received signature. Then payoad is Base64 decoded, and obtained nonce is encoded again, but along with the username, email and id of the user. Created payload is used again for generating new HMAC-SHA256. For this example, new sso and sig are presented below:

sso (payload) = "NmUwNzhlZDU0ZGQyMGMwYmVlMzg1ODVlMzJkZDA0YWE=\n"
sig (signature) = "37d27924092e4c8305938e4fa3b1822edaa668a852d5d4bc592075ace10ed2a1"
nonce = "6e078ed54dd20c0bee38585e32dd04aa"
password = "supersecure"
new sso = "NmUwNzhlZDU0ZGQyMGMwYmVlMzg1ODVlMzJkZDA0YWEmdXNlcm5hbWU9dXNl\nciZlbWFpbD11c2VyQGRpc2NvdXJzZS5wbCZleHRlcm5hbF9pZD0x\n"
new sig = "0aeab625d3cb8a5dd161cb024941494e5304bc9121d18532c0e7efb0443da3ce"

Both parameters (new sso and new sig) are passed back to Dicourse and compared in the same way as already shown. After encoding, application receives the user’s username, email and id, in a secure and controlled way. Discourse is also configured so that the user will be signed in and in case there is no such user a new one will be created. If Single-Sign-On is enabled the changes of email and username are not allowed. If user is signed in your application, he can create the same user on Discourse forum which means that you have integrated your application with great discussion platform with only few lines of code, nice!