Motivations

Some partners choose to bundle or embed Team-One such that some or all of Team-One's functionality is available to their customers (users).

In this scenario, end-user accounts are typically "shared" between the systems such that for every user account in the partner's system (that has access to Team-One) there is a corresponding user account in Team-One's system.

Before a user can be allowed to access Team-One, their identity must be validated. Typically this is achieved by asking the user to log in with a username and password. However, these shared users will have already authenticated (logged-in) to the partner's system. In order to provide a more seamless experience, we'd prefer not to ask the user to log in to each system independently,

This document describes the protocol by which a partner can securely communicate the end-user's authenticated identity to Team-One, allowing the user to bypass the typical Team-One log-in process.

Overview

The general workflow for this process is as follows:

  1. We will assume that the user's account exists in both systems, with some shared user identifier known to both systems (for example, a username, email address or account id).

  2. The end-user must log-in (or otherwise authenticate) to the partner system.

  3. The end-user tries to access Team-One, for example by following a link or opening a page that embeds the Team-One widget.

  4. In order to communicate the identity of that user to Team-One, the partner system constructs an "authentication message" that includes (among other data) the shared identifier for the authenticated user's account.

  5. In order to prove the authenticity of this message, the partner system cryptographically "signs" it using information known only to Team-One and the partner system. (This "shared-secret" acts as a sort of password to authenticate the partner system.)

  6. The partner system sends both the message and the signature to Team-One's system (typically by passing the information through the end-user's browser as a destination URL).

  7. Team-One independently computes the signature and compares it to the original in order to validate the authenticity of the message.

  8. Once the authenticity of the message has been established, Team-One's system trusts the user identifier provided by the partner system and allows the user to access Team-One as if she directly logged in to Team-One.

This signed-authentication-message construct is known as a Hash-based Message Authentication Code or "HMAC".

The Components of the Authentication Message

The authentication message is submitted as a set of key-value pairs.

The following keys are included:

  • v - the protocol version identifier (currently 100).
  • c - the Team-One client-id (application-id) for the partner system (as assigned by Team-One).
  • n - the "key-schedule", identifying the specific "shared secret" that will be used to sign the message.
  • a - the action conveyed by this message (e.g., login).
  • u - the shared user identifier (e.g., john.smith@example.com or c04df3e0-8a99-bbf4-dc7b-2d7e24f98134).
  • r - a "salt" or "nonce" value (a random positive integer newly generated with each message)
  • t - the date and time at which the message was constructed, in the ISO-8601 format (e.g., 2015-01-14T01:26:18.184Z for 1:26 AM on January 14th, 2015 (GMT)).

For example, the set of key-value pairs:

  • v = 100
  • n = 101
  • u = jane@example.org
  • a = login
  • c = 716b7969-34be-f684-4003-599f1e595b4f
  • r = 578945203
  • t = 2015-01-02T13:23:00.000Z

represents an authentication message generated on January 2, 2015 at 8:53 AM EST (1:23 PM UTC) for the user identified as jane@example.org by the partner system identified by 716b7969-34be-f684-4003-599f1e595b4f.

The v, c and n parameters allow Team-One to identify the partner system that created the message and the shared-secret value used to sign the message.

The a and u parameters clarify the action to be taken (in this case, log in as the specified user).

The t parameter protects against "replay" attacks. To prevent an attacker from re-using the message, the message is only valid for a short interval around the specified time.

Computing the Cryptographic Signature

Three steps are required to compute the cryptographic signature used to ensure the authenticity of the message.

  1. The collection of name/value pairs is converted to a string, using a query-string like syntax.

    Specifically:

    1. Each pair is converted to a string of the form <key>=<value>.
    2. These pairs are placed in alphabetical order (by key, such that a=<value> is first, c=<value> is second and so on).
    3. The ordered list of pairs is combined into a single string, using & as a delimiter.

      The resulting string resembles the query-string part of a URL, but no "URI-encoding" (%nn) is required or allowed. For example, while the value jane@example.org might be encoded jane%40example.com within the query string of a URL, the original string--jane@example.org--is used in the computation of the cryptographic signature.

    For example, the values above would be encoded as:

    a=login&c=716b7969-34be-f684-4003-599f1e595b4f&n=101&r=578945203&t=2015-01-02T13:23:00&u=jane@example.org&v=100
    
  2. This authentication-message string (converted to UTF-8 bytes if needed) is combined with a "shared secret" (a type of password known only to Team-One and the partner system) to compute an HMAC-SHA512 digest.

  3. This digest value (an array of bytes) is converted into an ASCII string using the Base64 encoding scheme. This Base64-encoded string is the message signature.

    For example, using the authentication-message string above and the shared secret the secret key, the message signature is:

    NEVda9xWpUHrwS1ElcV5x9boZ5s85GwHHBvMvAfJ9Ga2qbfsuKj/s5Eewsw1XgmtBiuXZLA1Ff5WzbltXjOi4Q==
    

Transmitting the Signed Message

Once computed, the signed authentication message is typically transmitted to Team-One as query-string parameters to a specified destination page.

To transmit the signed authentication message to a destination page on the Team-One site, the authentication message's key/value pairs are added as query-string parameters to the destination URL. In addition, the computed signature is added as a query-string parameter named s. For example, given the message and signature above, the following query string would be used:

?a=login&c=716b7969-34be-f684-4003-599f1e595b4f&n=101&r=578945203&t=2015-01-02T13:23:00.000Z&u=jane%40example.org&v=100&s=NEVda9xWpUHrwS1ElcV5x9boZ5s85GwHHBvMvAfJ9Ga2qbfsuKj%2Fs5Eewsw1XgmtBiuXZLA1Ff5WzbltXjOi4Q%3D%3D

Note that the order in which the parameters appear in the actual query string is not significant. The values will be independently parsed and re-combined to compute the signature.

Note that when passed within the query-string, the values of each parameter should be URI-encoded (as seen in the %40, %2F and %3D values above).

Validating the Signed Message

Validation of the cryptographic signature is Team-One's responsibility. A partner system will not need to perform this task. But for the sake of clarity we will describe the validation process here.

In order to validate the signed message, Team-One performs the following actions:

  1. The c (client id), v (version) and n (key-schedule) parameters are used to identify the partner system and look up the associated shared-secret key.
  2. Using the shared-secret and the authentication message values, Team-One independently computes the message signature. If the computed and given signatures do not match, the message is considered invalid.
  3. The given timestamp value (t) is compared to the system time on the Team-One server. If the difference between the times is too large, the message is considered invalid.
  4. The specified user (u) is examined to determine if the specified partner system (c) is authorized to authenticate on that user's behalf. If it is not, the message is considered invalid.

Implementation Examples

"Manually" Computing the Signature

For test or verification purposes, one can "manually" construct an authentication message and use local or online tools to compute the signature.

Let's assume:

  • You've been assigned the client identifier e236cbe26a1c2144373bf8309369c3bb.
  • You've been assigned the shared secret the-shared-secret identified by key number 203.
  • You have authenticated the user identified by user@example.com.

To create an authentication message, we must compute the following:

  • An arbitrary "seed" value. For the sake of this example, let's assume 8675309.
  • The current date and time, in ISO 8601 format. One way to obtain this value is to enter the following URL into your browser: javascript:alert(new Date().toISOString()). For the sake of this example, let's assume the value is 2015-01-02T13:23:00.000Z.

With these values in hand, we can construct the full base payload as described above, yielding:

a=login&c=e236cbe26a1c2144373bf8309369c3bb&n=202&r=8675309&t=2015-01-02T13:23:00.000Z&u=user@example.com&v=100

Next we must compute the SHA512 HMAC value for that message (as seen below).

Using Bash

With openssl installed, you can compute the signature on the command line, as follows:

echo <PAYLOAD> | openssl enc -sha512 -k "the-shared-secret" -base64 | tr '+/' '-_' | tr -d '=\n'

where <PAYLOAD> is the payload value constructed above.

Using Web-Based Tools

Although not affiliated with Team-One in any way, sites such as http://hash.online-convert.com/sha512-generator or http://www.freeformatter.com/hmac-generator.html can also be used to compute the signature.

Simply enter the payload value constructed above and the shared secret and submit the form.

Server-Side JavaScript (Node.js)

WARNING

You should NOT compute a signed authentication message within client-side code running in the end-user's browser, because doing so would make your "shared-secret" value public.

However, since JavaScript is a widely understood language and is increasingly popular as a server-side language, we present the following example of creating a signed authentication message using Node.js.

// Your client identifier, key schedule and shared-secret
// values will usually be "fixed".  They might come from a
// (secured) configuration file.
var clientId = 'e236cbe26a1c2144373bf8309369c3bb';
var keyNumber = 203;
var secret = 'the-shared-secret';

// The user identifier will often be based on user input.
var userId = 'test-user-one@team-one.com';

// The timestamp and random "seed" are generated.
var timestamp = (new Date()).toISOString();
var random = Math.round(Math.random()*999999);

// To create the message to be signed, we simply
// concatenate the parameters, in this order:
var payload = 'a=login';
payload += '&c='+clientId;
payload += '&n='+keyNumber;
payload += '&r='+random;
payload += '&t='+timestamp;
payload += '&u='+userId;
payload += '&v=100';

console.log('PAYLOAD     :',payload);

// To compute the signature in Node.js, we can use the
// `crypto` module, like this:
var hmac = require('crypto').createHmac('sha512', secret);
hmac.update(payload);
var signature = hmac.digest('base64');

console.log('SIGNATURE   :',signature);

// When used in a query string, we must append
// the signature to the payload, and also make
// sure to encode any characters that may not
// be safe to use in a URI.
var qs = 'a=login';
qs += '&c='+clientId;
qs += '&n='+keyNumber;
qs += '&r='+random;
qs += '&t='+timestamp;
qs += '&u='+encodeURIComponent(userId);
qs += '&v=100';
qs += '&s='+encodeURIComponent(signature);

console.log('QUERY STRING:',qs);

var baseUrl = "https://example.team-one.com/sso";
var fullUrl = baseUrl + "?" + qs;

console.log('FULL URL    :',fullUrl);

Java

// We'll use the Base64 encoder from the Apache Commons
// Codec module (from http://commons.apache.org/proper/commons-codec/).
import org.apache.commons.codec.binary.Base64;

// All of the other imports from the standard SDK.
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Formatter;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.Random;
import java.net.URLEncoder;

public class SSOviaHMAC {

  /**
   * Calculates the SHA-512 HMAC signature and prints it
   * to the console.
   */
  public static void main(String[] args) {

    // STEP ONE: Collect the data we'll need.
    ////////////////////////////////////////////////////////////

    // The `clientId`, `keyNumber` and `secret` values are
    // assigned to your application by Team-One.
    // They are more-or-less constant values.
    String clientId = "e236cbe2d6a1c2144373bf8309369c3bb";
    int keyNumber = 203;
    String secret = "the-shared-secret";

    // The `userId` string contains the shared identifier
    // that your system uses to tell Team-One which
    // account you are single-signing-in to.
    String userId = "test-user@example.com";

    // The `timestamp` and `randomInt` values are
    // newly generated with each request. (See
    // the utility methods defined below.)
    String timestamp = SSOviaHMAC.getISO8601Time();
    int randomInt = SSOviaHMAC.random.nextInt();

    // STEP TWO: Generate the message to be signed.
    ////////////////////////////////////////////////////////////

    // To create the `payload` we just concatenate
    // the message parameters, in this order:
    String payload = "a=login";
    payload += "&c=" + clientId;
    payload += "&n=" + keyNumber;
    payload += "&r=" + randomInt;
    payload += "&t=" + timestamp;
    payload += "&u=" + userId;
    payload += "&v=100";

    System.out.println("PAYLOAD: " + payload);

    // STEP THREE: Compute the signature.
    ////////////////////////////////////////////////////////////

    String signature = null;

    // To compute the signature, we...
    try {

      // ...compute the SHA512-HMAC digest of our
      // `payload` using the `secret` key value:
      Mac hmac = Mac.getInstance("HmacSHA512");
      SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), "HmacSHA512");
      hmac.init(key);
      byte[] digest = hmac.doFinal(payload.getBytes("UTF-8"));

      // ...and then encode the resulting bytes as
      // a Base64 string.
      signature = Base64.encodeBase64String(digest);

      System.out.println("SIGNATURE: " + signature);

     } catch (Exception e){
       System.err.println("Error computing signature: "+e);
     }

    // STEP FOUR: Assemble the query string.
    ////////////////////////////////////////////////////////////

    String qs = null;

    // Our query string contains the parameters
    // from our payload (URL-encoded as necessary)
    // in an arbitrary order, with the signature
    // added under the name `s`.
    try {
      qs  = "a=login";
      qs += "&c=" + clientId;
      qs += "&n=" + keyNumber;
      qs += "&r=" + randomInt;
      qs += "&t=" + timestamp;
      qs += "&u=" + URLEncoder.encode(userId, "UTF-8");
      qs += "&v=100";
      qs += "&s=" + URLEncoder.encode(signature, "UTF-8");
      System.out.println("QUERY STRING: " + qs);
     } catch (Exception e){
       System.err.println("Error computing query string: "+e);
     }

    // STEP FIVE: Create the full URL to send the user to.
    ////////////////////////////////////////////////////////////

    // Our final URL is some base URL:
    String baseURL = "https://example.team-one.com/sso";

    // ...with the query string appended:
    String fullURL = baseURL + "?" + qs;

    System.out.println("FINAL URL: " + fullURL);

    // THAT'S ALL
    ////////////////////////////////////////////////////////////

    // To log the user in to Team-One, we direct
    // his web browser to that final URL.

  }


  /**
   * The random number generator we use to generate "nonce-like" values for the `r` parameter.
   */
  private static Random random = new Random();


  /**
   * A utility method we use to generate values for the `t` parmeter.
   * @return the current date and time in the ISO 8601 format.
   */
  public static String getISO8601Time() {
    return String.format("%tFT%<tRZ", Calendar.getInstance(TimeZone.getTimeZone("Z")));
  }

}

Other Examples

Most modern programming languages provide libraries that support the computation of an HMAC value using SHA-512 and other hashing algorithms.

Here are a few links to example code, detailed documentation and library modules.

Figures

 
swimlane diagram

Sequence diagram of the end-to-end HMAC-based single-sign-on process.

 
 
 
flowchart

Flow-chart of the partner system's activity in the single-sign-on process.