Add-on Single Sign-on
Last updated February 06, 2024
Table of Contents
Your cloud service probably has a web UI admin panel that your users log into to manage their resources. For example, a Memcache cloud service may offer a dashboard for web-based usage analytics and the ability to flush a cache.
Heroku customers can access your dashboard as well if you implement single sign-on as described in this document.
This article documents how to implement SSO under the current v3 of the Add-on Partner API, and most examples reflect as such. We’ve noted the differences for legacy v1 integrations where necessary. You can find the API version your add-on is using in the Partner Portal under “Settings” -> “Provisioning API”.
Summary
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 create a user session and route them to the admin panel for their resource.
Testing SSO
We suggest creating a <your slug>-staging
version of your add-on to test SSO
and other add-on integration features. This <your slug>-staging
add-on should
point to staging versions of your integration infrastructure.
Salt, timestamp, and tokens
The add-on manifest is a JSON document that describes your add-on.
When you create an add-on (and consequently its manifest), the manifest fields include a randomly-generated sso_salt.
There are two tokens in an SSO request and either one may be used to log in the user, depending on what version of the Add-on Partner API you’re using. The v3 token is named resource_token
. The legacy v1 token is named token
.
Creating the Resource Token under v3
Under v3 of the Add-on Partner API, the resource_token is created using the following formula:
resource_token = sha1(resource_uuid + ':' + salt + ':' + timestamp)
resource_uuid
is the uuid that we provide for the resource in a provisioning request.salt
comes from your add-on manifest.timestamp
will be included in the parameters for the POST request.
For example, given these inputs:
resource_uuid = 11111111-1111-1111-1111-111111111111
salt = 2f97bfa52ca102f8874716e2eb1d3b4920ad0be4
timestamp = 1267597772
…the SSO resource_token will be:
SHA1("11111111-1111-1111-1111-111111111111:2f97bfa52ca102f8874716e2eb1d3b4920ad0be4:1267597772") =
4e9ce13ca328c6f3e2857b7de1724fd6c7c1c423
Creating the Resource Token under v1
Under v1 of the Add-on Partner API, the token is created using the following formula:
token = sha1(provider_id + ':' + salt + ':' + timestamp)
provider_id
is the value you created and returned from your cloud service during a response to provisioning request.salt
comes from your add-on manifest.timestamp
will be included in the parameters for the POST request.
For example, given these inputs:
provider_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.
sso_url
comes from your add-on manifest.resource_id
is the uuid that we provide for the resource in the provisioning call.timestamp
is a unix epoch timestamp.resource_token
is computed using the formula above. When using theresource_id
andresource_token
, theid
andtoken
values should be ignored.
Legacy fields that can safely be ignored are:
id
andtoken
– used only for the v1 salted token. Ignore if you are on v3 of the Add-on Partner API.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.
If the SHA1 hash you compute does not match the one passed in resource_token
(or token
if you’re using v1), 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.
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 timestamp
is current and the SHA1 hash matches, the user is authorized to access the resource in question.
You should create a user session through whatever method you normally use, possibly 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.
Display user information in your site’s layout
There are additional form-encoded parameters sent in the SSO request to help your site display a context for the user. These include:
user
- the email address of the current userapp
- the Heroku app name that is in scope and the user came from.
These can be used to populate a template for the user info (and potentially fetch their Gravatar) as well as link back to the Dashboard for the app in context. For that, assuming the param came in as &app=your-app-name
, link to https://dashboard.heroku.com/apps/your-app-name
.
Sample code
Here’s a sample implementation of a v3 single sign-on endpoint 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
Removing non-relevant page elements
Once you have your site accepting single sign-on requests, the final step is to hide (or disable) page elements that are not relevant for Heroku customer SSO sessions. Some examples include:
- Change password
- Change account name
- Update billing information
- Log out
Heroku customers will manage all of these items through the Heroku Dashboard and CLI, and your add-on cannot update them in Heroku’s infrastructure.