Add-on Single Sign-on
Last updated 29 March 2018
Your cloud service probably has a web UI admin panel that your users log into to manage and view their resources. For example, a Memcache cloud service would probably offer a dashboard for web-based usage analytics and the ability to flush a cache.
Heroku customers will be able to access the admin panel for their resource if you implement single sign-on as described in this document.
Heroku will generate a single sign-on token by combining the salt (a shared secret), timestamp, and resource ID. The user’s browser will be redirected to your site with this token. Your site can confirm the authenticity of the token, then set a cookie for the user session and redirect them to the admin panel for their resource. Pages displayed during the session on your site must inject the HTML for the Heroku nav header. Test this with
kensa test sso, or try in your browser with
Salt, timestamp, and token
Manifest The add-on manifest is a JSON document that describes your add-on. Read the full reference
When you init an add-on manifest, the fields include a randomly-generated sso_salt field:
$ kensa init Initialized new addon manifest in addon-manifest.json $ grep salt addon-manifest.json "sso_salt": "2f97bfa52ca102f8874716e2eb1d3b4920ad0be4"
There are two tokens and either one may be used to log in the user when they are redirected to your site. The prefered token is named
resource_token. The legacy token is named
The resource_token is created using the following formula:
resource_token = sha1(resource_id + ':' + salt + ':' + timestamp)
The resource_id is the uuid that we provide for the resource in the provisioning call. The salt comes from the manifest. The timestamp will be included in the parameters for the POST request.
For example, given these inputs:
resource_id = 11111111-1111-1111-1111-111111111111 salt = 2f97bfa52ca102f8874716e2eb1d3b4920ad0be4 timestamp = 1267597772
…the SSO resource_token will be:
SHA1("11111111-1111-1111-1111-111111111111:2f97bfa52ca102f8874716e2eb1d3b4920ad0be4:1267597772") = 4e9ce13ca328c6f3e2857b7de1724fd6c7c1c423
[Legacy] Provider Id Token
The token is created using the following formula:
token = sha1(id + ':' + salt + ':' + timestamp)
The id is the value returned from your cloud service on a provisioning call, and is referred to as the provider_id in other contexts. The salt comes from the manifest. The timestamp will be included in the parameters for the POST request.
For example, given these inputs:
id = 123 salt = 2f97bfa52ca102f8874716e2eb1d3b4920ad0be4 timestamp = 1267597772
…the SSO token will be:
SHA1("123:2f97bfa52ca102f8874716e2eb1d3b4920ad0be4:1267597772") = bb466eb1d6bc345d11072c3cd25c311f21be130d
Signing in the user on redirect
When the user clicks your add-on in their add-on menu, they will be directed via HTTP POST to a URL defined in your manifest.
Requests will look like:
POST <production/sso_url> resource_id=<resource_id>&resource_token=<resource_token>&id=<id>&token=<token>×tamp=<timestamp>&nav-data=<nav data>&email=<user's email address>
As shown, the data is form-encoded in the POST body. The hostname or
sso_url comes from your add-on manifest. The resource_id is the uuid that we provide for the resource in the provisioning call. The timestamp is a time_t, and the resource_token is computed using the formula above. Nav data contains information like the current app name and installed add-ons so the Heroku layout can build the appropriate view for the current app. When using the resource_id and resource_token, the id and token should be ignored.
If you are using the legacy token, then you should ignore the resource_id, and resource_token, and instead use the id which is the provider id included in your response to a provisioning request, and the token, which is calculated using the formula above.
HTTP 403: Forbidden HTTP status code 403 indicates that the user was not allowed access to this page. You can return this code and still render a normal, human-readable page for them, perhaps suggesting that they contact support if they believe their request is legitimate.
If the resource_token you compute does not match the one passed in the query parameters, the user should be shown a page with an HTTP status code of 403. If the timestamp is older than five minutes, they should also see a 403.
If the timestamp is current and the resource_token matches, you should create a user session through whatever method you normally use, most likely setting a cookie. The session should also store that it is a Heroku single sign-on, since what is displayed will be slightly different for Heroku customers than users logging in through your regular standalone service. Because user access can change at any time this SSO data should only be trusted for the duration of a session. We also suggest limiting session lifespan to 90 minutes.
Here’s a sample implementation of single sign-on written in Ruby/Sinatra:
post "/heroku/sso" do pre_token = params[:resource_id] + ':' + HEROKU_SSO_SALT + ':' + params[:timestamp] token = Digest::SHA1.hexdigest(pre_token).to_s halt 403 if token != params[:resource_token] halt 403 if params[:timestamp].to_i < (Time.now - 2*60).to_i account = Account.find(params[:resource_id]) halt 404 unless account session[:user] = account.id session[:heroku_sso] = true redirect "/dashboard" end
Rendering the nav header
The Heroku nav header should be shown on all pages for the duration of the customer’s stay on your site.
Removing non-relevant page elements
Once you have your site accepting single sign-on requests and rendering the nav header, the final step is to look for page elements that are not relevant for Heroku customer sessions. Some examples include:
- Change password
- Change account name
- Update billing information
- Log out
These should be hidden in Heroku sessions via conditionals in your page rendering.
Testing SSO with Kensa
You can try your single sign-on implementation in your browser with this command:
$ kensa sso 123 Opening http://localhost:3000/heroku/sso
Provide the ID of a previously provisioned resource as an argument, and kensa will construct the same URL that Heroku would when initiating a single sign-on session.
You can also run a set of automated tests to confirm your single sign-on implementation works correctly and respects all the standards described in this document:
$ kensa test sso 123 Testing POST /heroku/sso Check validates token [PASS] Check validates timestamp [PASS] Check logs in [PASS] Check creates the heroku-nav-data cookie [PASS] Check displays the heroku layout [PASS] done.