2. Authentication

As previously mentioned, each Api request must be authenticated prior to be processed. Therefore, client-side applications will need to sign each request with a specific authorization header.

When working on the authentication mechanism, the development team took as a basis the Amazon AWS experience with signing the requests.

Building HMAC Signature

Customate uses HMAC signature to identify each incoming request, making sure it was generated by a concrete user, and wasn’t changed en route.

Signature consists of two elements: api key and access token:

Authorization: Signature {ApiKey}:{AccessToken}

ApiKey is provided to clients during registration for working with Customate Api, AccessToken generation formula looks like this:

AccessTokenSource = EncodeUtf8("{HttpMethod}\n{HttpPath}\n{ContentType}\n{FormattedCustomateHttpHeaders}")
AccessTokenHashInHex = ConvertToHex(HmacSha256(AccessTokenSource, ApiSecret))
AccessToken = DecodeUtf8(Base64(AccessTokenHashInHex))

AccessToken components and signing algorithm described in the following example.

Imagine that the client want to sign the following request:

POST /v1/profiles/17410303-d336-4b1a-bf17-260bc80d9741/verification?force_verification=false HTTP/1.1
Content-Type: application/json

{
  "birth_country": "IE",
  "mother_maiden_name": "Smithy",
  "passport": {
    "origin_country": "GB",
    "number": "PD12345678IRL1234567M1234567<<<<<<<<<<<<<<<0",
    "expiry_date": "2031-09-23"
  },
  "driver_licence": {
    "number": "EUEGE123456BM9NM",
    "postcode": "B126DY",
    "issue_date": "2011-10-27"
  }
}

 

Customate Api requires three custom headers for request:

  • PaymentService-ContentHash contains SHA-1 hash of the request payload (header will be skipped if it’s GET/DELETE request, see notes below), pseudo code looks similar to AccessToken formula:
ContentHashSource = EncodeUtf8(RequestBody)
PaymentService-ContentHash = ConvertToHex(Sha1(ContentHashSource))
  • PaymentService-Nonce must contain a unique UUID for each request.
  • PaymentService-Date contains a datetime value in ISO 8601 format.

These headers take part in the AccessTokenSource generation as FormattedCustomateHttpHeaders element.

This original string will consist of next values (based on the discussed request example):

  • HttpMethod: POST
  • HttpPath: /v1/profiles/17410303-d336-4b1a-bf17-260bc80d9741/verification (query parameters are not included)
  • ContentType: application/json (could be empty for GET/DELETE requests, see notes below)
  • FormattedCustomateHttpHeaders: paymentservice-contenthash:cO2anfgUtTeKzMymzHYvOVzWQR4xXzgw/dDjML9hxwI=\npaymentservice-date:2020-04-12T14:52:00Z\npaymentservice-nonce:c189b551-4ede-472c-9145-872e158ee606

FormattedCustomateHttpHeaders template looks like this:

paymentservice-contenthash:{contentHash}\npaymentservice-date:{date}\npaymentservice-nonce:{nonce}

where contentHash could be an empty string (for GET/DELETE requests for example).

Summarizing the listed points, the client will get similar value: 

POST\n/v1/profiles/17410303-d336-4b1a-bf17-260bc80d9741/verification\napplication/json\npaymentservice-contenthash:66274ef1a86b55f31e805dc171cae5e5efcab455\npaymentservice-date:2020-04-12T14:52:00Z\npaymentservice-nonce:c189b551-4ede-472c-9145-872e158ee606

 

This string is passed to HMAC-SHA-256 function and its result (hash will return bytes) must be converted to HEX, then the client passes hex representation to base64 function, and add the resulting value to Authorization header and get something similar to:

Authorization: Signature 04324b7a-dadc-41b1-aa77-5fb52c0aacf2:NjhlNjU2YjI1MWU2N2U4MzU4YmVmODQ4M2FiMGQ1MWM2NjE5ZjNlN2ExYTlmMGU3NTgzOGQ0MWZmMzY4ZjcyOA==

In this example our Api key is 04324b7a-dadc-41b1-aa77-5fb52c0aacf2.

Final request will look similar to this:

POST /v1/profiles/17410303-d336-4b1a-bf17-260bc80d9741/verification?force_verification=false HTTP/1.1
Content-Type: application/json
PaymentService-ContentHash: 66274ef1a86b55f31e805dc171cae5e5efcab455
PaymentService-Nonce: c189b551-4ede-472c-9145-872e158ee606
PaymentService-Date: 2020-04-12T14:52:00Z
Authorization: Signature 04324b7a-dadc-41b1-aa77-5fb52c0aacf2:NjhlNjU2YjI1MWU2N2U4MzU4YmVmODQ4M2FiMGQ1MWM2NjE5ZjNlN2ExYTlmMGU3NTgzOGQ0MWZmMzY4ZjcyOA==

In general terms, the algorithm can be reduced to the following points:

  1. Obtain Api key and secret
  2. Generate Authorization header:
    1. Generate AccessToken:
      1. Assign HTTP request method to HttpMethod variable.
      2. Assign HTTP request path to HttpPath variable.
      3. Assign empty string to ContentType variable if Content-Type header will not be sent, otherwise assign value from this header.
      4. Use an empty string as a value for PaymentService-ContentHash header if HttpMethod is GET/DELETE, otherwise encode request body in UTF-8, generate SHA-1 hash for it and assign HEX representation to PaymentService-ContentHash header.
      5. Generate a random UUID and set it to PaymentService-Nonce header. Take current time, convert it to ISO 8601 format and set to PaymentService-Date header.
      6. Convert to lower-case (only names) and combine PaymentService-ContentHash, PaymentService-Nonce and PaymentService-Date headers in one string, use newline as a separator. Assign result to FormattedCustomateHttpHeaders variable.
      7. Form a string for token generation based on template: {HttpMethod}\n{HttpPath}\n{ContentType}\n{FormattedCustomateHttpHeaders}
      8. Encode this string in UTF-8. Generate HMAC-SHA-256 hash for it using APi secret and convert the result to HEX. Encode the string using Base64 algorithm and assign it to the AccessToken variable.
    2. Form Authorization header value using template: Signature {ApiKey}:{AccessToken}
  3. Use Authorization header in the request to Customate Api.

Some important notes to AccessToken generation:

  • Lower-case headers names for FormattedCustomateHttpHeaders
  • Headers in FormattedCustomateHttpHeaders element are sorted by header name.
  • PaymentService-Nonce value should be unique for all requests. It’s possible that Customate Api will start to verify it in the near future.
  • Remove any whitespace around the colon in the header.
  • Separate headers by newlines (‘\n’).
  • The Content-Type header could be skipped for GET/DELETE requests, in this case a client must still insert an empty string at the point in AccessTokenSource where this value would normally be inserted (don’t forget about newline). Just note that some libraries will still insert that header, even if it wasn’t included intentionally, so clients should pay attention to this nuance.
  • The PaymentService-ContentHash header doesn’t make sense for GET/DELETE requests (some http libraries may also drop a header with an empty value) and should be skipped, but for consistency Customate Api will expect an empty string as its value in FormattedCustomateHttpHeaders element (not the http request itself), like: paymentservice-contenthash:\n See example below.
  • Some toolkits make it difficult to manually set value for the standard Date header, that’s why the development team introduced a custom date header: PaymentService-Date. The value of this header must be in ISO 8601 format (e.g. YYYY-MM-DDThh:mm:ssTZD).
  • The value of the PaymentService-Date header must specify a time no more than 5 minutes away from the Customate server’s clock (which is periodically synchronized with the NTP server).
  • The string to sign (AccessTokenSource) must be UTF-8 encoded.
  • Query parameters (force_verification in example above) are skipped.
  • Prefix with the Api version (/v1 in our examples) is part of the url and should be included in the HttpPath
  • Customate Api uses HMAC-SHA256 hash function (as a more secure alternative to SHA-1) to compute the signature.
  • Clients should make sure it sends the request body in correct format and Content-Type matches this format.

Example

The client needs to sign next request:

GET /v1/profiles/17410303-d336-4b1a-bf17-260bc80d9741 HTTP/1.1
PaymentService-Date: 2020-04-12T15:52:00.121Z
PaymentService-Nonce: 59cd6e82-e807-44a7-9965-ee2394f0a7f4

For this example, the original access token string to be signed is (note included newline even though there is no Content-Type header in the request):

GET\n/v1/profiles/17410303-d336-4b1a-bf17-260bc80d9741\n\npaymentservice-contenthash:\npaymentservice-date:2020-04-12T15:52:00.121Z\npaymentservice-nonce:59cd6e82-e807-44a7-9965-ee2394f0a7f4

 

Suppose client’s Api key is d5fee211-bbef-4cae-94a0-4ba62dec82dd and Api secret is 1ejIyoMIHV0WTF9J7ow7m9TkkYBCecqbdMcL98jaOFEGOqKqX7TtJy8dVqqn.

Then we can compute the signature as follows (Python):

import base64
import hmac
import hashlib

api_secret = "1ejIyoMIHV0WTF9J7ow7m9TkkYBCecqbdMcL98jaOFEGOqKqX7TtJy8dVqqn".encode('utf-8')
string_to_sign = "GET\n/v1/profiles/17410303-d336-4b1a-bf17-260bc80d9741\n\npaymentservice-contenthash:\npaymentservice-date:2020-04-12T15:52:00.121Z\npaymentservice-nonce:59cd6e82-e807-44a7-9965-ee2394f0a7f4".encode('utf-8')
hmac_hash = hmac.new(api_secret, string_to_sign, hashlib.sha256)

result = base64.b64encode(hmac_hash.digest()).decode('utf-8')

 

The resulting access token would be ZDM1YzRhYjM0ODQxYTFhYWExN2RhMzQzM2UzODc0YTA4YWM5YTIxN2Q2OGIwODhhY2JjZmRkMjA4ZjE5ZDQ4NQ==, the client adds the Authorization header to his request to come up with the following result (note that Content-Type and PaymentService-ContentHash headers are missing):

GET /v1/profiles/17410303-d336-4b1a-bf17-260bc80d9741 HTTP/1.1
PaymentService-Date: 2020-04-12T15:52:00.121Z
PaymentService-Nonce: 59cd6e82-e807-44a7-9965-ee2394f0a7f4
Authorization: Signature d5fee211-bbef-4cae-94a0-4ba62dec82dd:ZDM1YzRhYjM0ODQxYTFhYWExN2RhMzQzM2UzODc0YTA4YWM5YTIxN2Q2OGIwODhhY2JjZmRkMjA4ZjE5ZDQ4NQ==