Generating authorization grant tokens

Warning

This document describes an integration mechanism that is undergoing early-stage testing. The details of the token format may change in the future.

In order to allow your users (i.e. those whose accounts and authentication status you control) to annotate using a copy of Hypothesis embedded on your pages, you can ask us to register your site as a special kind of OAuth client.

We will issue you with a Client ID and a Client secret. These will allow you generate time-limited “authorization grant tokens” which can be supplied as configuration to the Hypothesis sidebar, and which will allow your users to annotate without needing to log in again. This document describes how to generate those tokens.

Overview

You will have been provided with the following:

Client ID
A unique identifier for your client account. It’s a UUID and will look something like this: 4a2fa3b4-c160-4436-82d3-148f602c9aa8
Client secret
A secret string which you MUST NOT reveal publicly, and which is used to cryptographically sign the grant tokens you will generate.

In addition, you will have provided us with what we call an “authority” for your account. The authority is a DNS domain name and acts as a unique namespace for your user’s accounts. For example, if your site lives at https://example.com, you may choose to use example.com as your authority, although we do not currently require any particular correspondence between your web address and your account authority.

You will use these three pieces of information, in combination with your user’s unique usernames, to generate the grant token.

The grant token is a JSON Web Token (JWT) and we strongly recommend that you use an existing JWT library for your programming environment if you can. You can find a list of JWT client libraries here.

Token format

A grant token is a JWT, signed with the Client secret using the HS256 algorithm, with a payload in a specific format, given below. Let’s assume that:

  • Your Client ID is 4a2fa3b4-c160-4436-82d3-148f602c9aa8.
  • Your authority is customwidgets.com.
  • The user has a username of samina.mian.
  • The current time is 2016-11-08T11:35:45Z, which corresponds to a UTC Unix timestamp of 1478601345.
  • The token should be valid for a few minutes, e.g. until 1478601645, expressed as a UTC Unix timestamp. The server limits the lifetime of a token (the difference between the nbf and exp timestamps) to 10 minutes.
  • The token should be valid for the annotation service running at hypothes.is.

With these data, we can construct a token payload. It should look like the following:

{
  "aud": "hypothes.is",
  "iss": "4a2fa3b4-c160-4436-82d3-148f602c9aa8",
  "sub": "acct:samina.mian@customwidgets.com",
  "nbf": 1478601345,
  "exp": 1478601645
}

You should sign this payload using the HS256 JWT-signing algorithm, using the Client secret as the key. The result will look something like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI0QTJGQTNCNC1DMTYwLTQ0MzYtODJEMy0xNDhGNjAyQzlBQTgiLCJuYmYiOjE0Nzg2MDEzNDUsImF1ZCI6Imh5cG90aGVzLmlzIiwiZXhwIjoxNDc4NjAxOTQ1LCJzdWIiOiJhY2N0OnNhbWluYS5taWFuQGN1c3RvbXdpZGdldHMuY29tIn0.65-ZErbLu1q8LpT_K8FAOQO984hAyN1XFBe1rC3lgfk

See also

RFC7523, “JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants”. Note that we currently only support the HS256 signing algorithm, and not the public-key RS256 signing algorithm mentioned in the RFC.

Examples

This section contains complete example code for generating a JWT in various common programming environments.

Python

We recommend using PyJWT:

import datetime
import jwt

# IMPORTANT: replace these values with those for your client account!
CLIENT_AUTHORITY = 'customwidgets.com'
CLIENT_ID        = '4a2fa3b4-c160-4436-82d3-148f602c9aa8'
CLIENT_SECRET    = '5SquUVG0Tpg57ywoxUbPPgjtK0OkX1ttipVlfBRRrpo'

def generate_grant_token(username):
   now = datetime.datetime.utcnow()
   userid = 'acct:{username}@{authority}'.format(username=username,
                                                 authority=CLIENT_AUTHORITY)
   payload = {
      'aud': 'hypothes.is',
      'iss': CLIENT_ID,
      'sub': userid,
      'nbf': now,
      'exp': now + datetime.timedelta(minutes=10),
   }
   return jwt.encode(payload, CLIENT_SECRET, algorithm='HS256')

Ruby

We recommend using ruby-jwt:

require 'jwt'

# IMPORTANT: replace these values with those for your client account!
CLIENT_AUTHORITY = 'customwidgets.com'
CLIENT_ID        = '4a2fa3b4-c160-4436-82d3-148f602c9aa8'
CLIENT_SECRET    = '5SquUVG0Tpg57ywoxUbPPgjtK0OkX1ttipVlfBRRrpo'

def generate_grant_token(username)
  now = Time.now.to_i
  userid = "acct:#{username}@#{CLIENT_AUTHORITY}"
  payload = {
    aud: "hypothes.is",
    iss: CLIENT_ID,
    sub: userid,
    nbf: now,
    exp: now + 600
  }
  JWT.encode payload, CLIENT_SECRET, 'HS256'
end