Authentication

Overview

Summon Search API uses an authentication scheme based on secret key verification via an HMAC-SHA1 digest. The API performs both authentication and authorization via the same HTTP Authorization header. Creating an Authorization header for an API request requires an access ID, a paired secret key, a client key, and access to certain elements of the HTTP request being authenticated. To create the Authorization header, specific request elements must be assembled into a string that uniquely identifies the request. This ID string is then turned into a digest using the HMAC-SHA1 algorithm as defined in RFC 2104. The digest is then Base64-encoded according to RFC 2045, and assembled into a header along with the access ID.

When the API server receives a request, the first thing it does is check the x-summon-date header to make sure it is within a reasonable margin of the server time. If the header time is not within one hour of the server time then authentication fails immediately. If the request timestamp is acceptable, then the server goes on to check that Summon authentication is being used in the Authorization header. If Summon authentication is being used, the server extracts the access ID, client key and encoded authentication digest from the Authorization header, and looks up the paired secret key using the access ID. Finally the server performs the same algorithm described in the previous paragraph to produce a test digest, and compares the test digest against the digest provided in the Authorization header. If the two digests are identical, the request is authenticated.

Example - A correctly constructed Search API request

GET /2.0.0/search?s.q=forest&s.ff=ContentType,or,1,15 HTTP/1.1
Host: api.summon.serialssolutions.com
Accept: application/xml
x-summon-date: Tue, 30 Jun 2009 12:10:24 GMT
x-summon-session-id: Jp+vWdRgypOOrJQPdzc86mOWFVo=
Authorization: Summon test;rYiYzRaaZ9/QYpiQFZADqpkgJfM=

Assembling The Identification String

The identification string is made up of the following five components:

  1. Accept header value
  2. x-summon-date header value
  3. Host header value
  4. Path portion of the resource URI
  5. Sorted query string unencoded

ID string components are appended to the ID string with a newline ('\n') character after each component. The last component has a trailing newline character like all of the other components, so the last character in the ID string is always a newline.

The first component in the request ID string is the content type as it appears in the Accept header. This will be either application/xml or application/json.

The second component is the date string as it appears in the x-summon-date header. The date string must conform to RFC 2616. The time represented by the date string must be the current time. The request will fail to authenticate if the timestamp differs too much from the server time.

The third component is the host as it appears in the Host header. The host will have just the host name.

The fourth component is the path. The path is everything in the URI that comes after the host and before the query string.

The final component in the ID string is a concatenated list of query parameters, unencoded, sorted in alphabetical order, and separated by '&'.

Example - Constructing an ID string from a request

The request:
 
  GET /2.0.0/search?s.q=forest&s.ff=ContentType,or,1,15 HTTP/1.1
  Host: api.summon.serialssolutions.com
  Accept: application/xml
  x-summon-date: Tue, 30 Jun 2009 12:10:24 GMT
  x-summon-session-id: Jp+vWdRgypOOrJQPdzc86mOWFVo=
 
The constructed ID string:
 
  "application/xml" + "\n" +
  "Tue, 30 Jun 2009 12:10:24 GMT" + "\n" +
  "api.summon.serialssolutions.com" + "\n" +
  "/2.0.0/search" + "\n" +
  "s.ff=ContentType,or,1,15" + "&" + "s.q=forest" + "\n"

Example - Java code for building an ID string

  private String computeIdString(String acceptType, String date, String host, String path, Map<String, String[]> queryParameters) {
    return appendStrings(acceptType, date, host, path, computeSortedQueryString(queryParameters));
  }

  private String computeSortedQueryString(Map<String, String[]> queryParameters) {
    List<String> parameterStrings = new ArrayList<String>();

    // for each parameter, get its key and values
    for (Map.Entry<String, String[]> entry : queryParameters.entrySet()) {

      // for each value, create a string in the format key=value
      for (String value : entry.getValue()) {
        parameterStrings.add(entry.getKey() + "=" + value);
      }
    }

    // sort the individual parameters
    Collections.sort(parameterStrings);
    StringBuilder queryString = new StringBuilder();

    // append strings together with the '&' character as a delimiter
    for (String parameterString : parameterStrings) {
      queryString.append(parameterString).append("&");
    }

    // remove any final trailing '&'
    if (queryString.length() > 0) {
      queryString.setLength(queryString.length() - 1);
    }
    return queryString.toString();
  }

  // append the strings together with '\n' as a delimiter
  public static String appendStrings(String... strings) {
    StringBuilder stringBuilder = new StringBuilder();
    for (String string : strings) {
      stringBuilder.append(string).append("\n");
    }
    return stringBuilder.toString();
  }

Computing the Digest

The digest is computed using the HMAC-SHA1 algorithm as defined in RFC 2104. Then it is encoded using Base64 encoding as defined in RFC 2045. The ID string must be UTF-8 encoded.

Example - Building a digest from an ID string

The constructed ID string:
 
  "application/xml" + "\n" +
  "Tue, 30 Jun 2009 12:10:24 GMT" + "\n" +
  "api.summon.serialssolutions.com" + "\n" +
  "/2.0.0/search" + "\n" +
  "s.ff=ContentType,or,1,15" + "&" + "s.q=forest" + "\n"
 
Hypothetical secret key:
 
  ed2ee2e0-65c1-11de-8a39-0800200c9a66
 
Computed digest:
 
  3a4+j0Wrrx6LF8X4iwOLDetVOu4=

Example - Java code for building a digest from an ID string

public static String buildDigest(String key, String idString) throws SignatureException {
  try {
    String algorithm = "HmacSHA1";
    Charset charset = Charset.forName("utf-8");
    SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), algorithm);
    Mac mac = Mac.getInstance(algorithm);
    mac.init(signingKey);
    return new String(Base64.encodeBase64(mac.doFinal(idString.getBytes(charset))), charset);
  } catch (Exception e) {
    throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
  }
}

The Base64 class used in the above code is from Apache Commons.


Assembling the Authorization Header

The Authorization header is made up of the following three parts:

  • The authentication scheme identifier -- Summon
  • The access ID -- (i.e.; the client shortname - <shortname>.summon.serialssolutions.com)
  • The HMAC-SHA1 digest

Summon comes first followed by a space. Then the access ID followed by a semicolon and no space. The HMAC-SHA1 digest comes last.

Example - Building an Authorization header

Access ID:
 
  test
 
HMAC-SHA1 Digest:
 
  rYiYzRaaZ9/QYpiQFZADqpkgJfM=
 
Formatted as an Authorization header string:
 
  Summon test;rYiYzRaaZ9/QYpiQFZADqpkgJfM=

If a given access ID has access to multiple client keys then an additional client key component must be added to the Authorization header. The client key comes after the access ID, and before the HMAC-SHA1 digest. Like the access ID, it is separated by a semicolon and no space.

Example - Building an Authorization header with a client key

Access ID:
 
  test
 
HMAC-SHA1 Digest:
 
  rYiYzRaaZ9/QYpiQFZADqpkgJfM=
 
Formatted as an Authorization header string:
 
  Summon test;rYiYzRaaZ9/QYpiQFZADqpkgJfM=