REST API Authentication

This guide is meant to provide the basic understanding and ability to authenticate a Service Account for REST API integrations. To get you up-and-running quickly, core concepts of the authentication flow are only explained briefly. To test the implementation, a call that fetches all devices in a project will be executed.

Service Account credentials

You need to have created a Service Account with access to a project and noted down its e-mail, the key ID, and it's secret. For this, see Service Accounts.

OAuth2 Authentication

All our REST APIs are secured via an OAuth2 authentication flow, using Service Accounts for access control and JWT as the medium for the exchange. This is the recommended way of authenticating an integration beyond simple prototyping.

Code Sample

If you want to get up-and-running quickly with authenticating the API, the implementation is provided in a few languages below. Be sure to exchange the fields in the form '<VARIABLE_NAME>' with your own details. For those interested, a more detailed explanation of the steps can be found below.

Python JavaScript
# packages
import sys
import time
import jwt      # pip install pyjwt==2.0.0
import requests # pip install requests==2.25.1

# authentication details
SERVICE_ACCOUNT_KEY    = '<YOUR_SERVICE_ACCOUNT_KEY_ID>'
SERVICE_ACCOUNT_EMAIL  = '<YOUR_SERVICE_ACCOUNT_EMAIL>'
SERVICE_ACCOUNT_SECRET = '<YOUR_SERVICE_ACCOUNT_SECRET>'
PROJECT_ID             = '<YOUR_PROJECT_ID>'
AUTH_ENDPOINT          = 'https://identity.disruptive-technologies.com/oauth2/token'


if __name__ == '__main__':
    # construct JWT header
    jwt_headers = {
        'alg': 'HS256',
        'kid': SERVICE_ACCOUNT_KEY,
    }
    
    # construct JWT payload
    jwt_payload = {
        'iat': int(time.time()),        # current unixtime
        'exp': int(time.time()) + 3600, # expiration unixtime
        'aud': AUTH_ENDPOINT,           # intended recepient
        'iss': SERVICE_ACCOUNT_EMAIL,
    }

    # sign and encode JWT
    encoded_jwt = jwt.encode(
        payload   = jwt_payload,
        key       = SERVICE_ACCOUNT_SECRET,
        algorithm = 'HS256',
        headers   = jwt_headers,
    )

    # exchange JWT for access token
    # note: the requests package does form url encoding by default
    access_token_response = requests.post(
        url = AUTH_ENDPOINT,
        headers = {'Content-Type': 'application/x-www-form-urlencoded'},
        data = {
            'assertion': encoded_jwt,
            'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer'
        }
    )

    # halt at response error
    if access_token_response.status_code != 200:
        print('Status Code: {}'.format(access_token_response.status_code))
        print(access_token_response.json())
        sys.exit()

    # isolate access token
    access_token = access_token_response.json()['access_token']

    # test that it works by requesting project device list
    response = requests.get(
        url = 'https://api.disruptive-technologies.com/v2/projects/{}/devices'.format(PROJECT_ID),
        headers = {'Authorization': 'Bearer ' + access_token},
    )

    # print response data
    print(response.json())

 

The authentication flow

All our REST APIs are secured via an OAuth2 authentication flow, using Service Accounts for access control and JWT as the medium for the exchange.

Authenticating a client is a 3 step process: 

1. Create JWT 2. Exchange for Access Token 3. Access API with Access Token
step1-create-jwt.svg

1. Create JWT

A JSON Web Token (JWT) contains three fields:

  1. Header: Token type and signature algorithm.
  2. Payload: Claims and additional data.
  3. Signature: A signature calculated of the entire JWT + a private secret.

Before being sent, these fields are each Base64Url encoded. They are combined in a compact dot-format in the form Base64Url(header).Base64Url(payload).Base64Url(signature), which is what we will refer to as the encoded JWT.

JWT.io

This guide will not cover much more details of JWT. For a great introduction on JWT that provides an interactive editor and an exhaustive list of client libraries, please see jwt.io.

Using your Service Account credentials, construct the JWT headers and payload. The timeframe from iat, issued at, to exp, expiration time, is 1 hour.

Python JavaScript
# construct JWT header
jwt_headers = {
    'alg': 'HS256',
    'kid': SERVICE_ACCOUNT_KEY,
}

# construct JWT payload
jwt_payload = {
    'iat': int(time.time()),        # current unixtime
    'exp': int(time.time()) + 3600, # expiration unixtime
    'aud': AUTH_ENDPOINT,           # intended recipient
    'iss': SERVICE_ACCOUNT_EMAIL,
}

The simplest way of Base64-encoding and signing our JWT is to use some language-specific library. This is available in most languages. You can, of course, perform the encoding and hashing process manually if you so choose.

Python JavaScript
# sign and encode JWT
encoded_jwt = jwt.encode(
    payload   = jwt_payload,
    key       = SERVICE_ACCOUNT_SECRET,
    algorithm = 'HS256',
    headers   = jwt_headers,
)

2. Exchange JWT for Access Token

The encoded JWT is exchanged for an Access Token by sending a POST request to the same endpoint used to construct the JWT:

https://identity.disruptive-technologies.com/oauth2/token

The POST request header should include a Content-Type field indicating the format of the body. Additionally, the POST request body is Form URL-Encoded and contains the following fields:

  1. "assertion" - Contains the encoded JWT string.
  2. "grant_type" - Contains the string "urn:ietf:params:oauth:grant-type:jwt-bearer". This specifies that you want to exchange a JWT for an Access Token.

It is important to note that the data has to be Form URL-Encoded. Some HTTP libraries, like Python's requests, does this by default and require no further input by the user. This is, however, not the norm and likely requires an additional step before sending the request. The URL Form Encoded data should take the form

assertion=Base64Url(header).Base64Url(payload).Base64Url(signature)&grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer

where the header, payload, and signature are found in the previous step.

Python JavaScript
# exchange JWT for access token
# NB: the requests package does form-encoding by default
access_token_response = requests.post(
    url  = AUTH_ENDPOINT,
    headers = {'Content-Type': 'application/x-www-form-urlencoded'},
    data = {
        'assertion': encoded_jwt,
        'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer'
    }
)

# isolate access token
access_token = access_token_response.json()['access_token']

The access token response can be expected to have the following format. It will be valid for 1 hour only. To refresh the token, perform the previous steps again.

{
    "access_token": "d663e83546294b158fea2574a1945319",
    "token_type": "bearer",
    "expires_in": 3599
}

3. Access API with Access Token

Once you have the Access Token, you need to include this with every call to the API. This can be achieved by including the Authorization header in the form shown in the snippet below.

Python JavaScript
# test that it works by requesting project device list
response = requests.get(
    url = 'https://api.disruptive-technologies.com/v2/projects/{}/devices'.format(PROJECT_ID),
    headers = {'Authorization': 'Bearer ' + access_token},
)

# print response data
print(response.json())

Common Errors

For an overview of all REST API errors, please see our Error Codes Reference.

  • [400] {'error': 'invalid_grant', 'error_description': 'Signature has expired'}
    Authentication Error. Your JWT timeframe is invalid. The fields iat, issued at, and exp, expiration time should be now and one hour in the future respectively, both in unixtime.
  • [400] {'error': 'invalid_grant'}
    Authentication Error.
    Might be caused by any of the following reasons:
    • The Service Account may not exist.
    • The Service Account key may not exist.
    • The assertion (encoded JWT) is missing or malformed. Make sure it is Form URL-encoded as described above.
  • [400] {error: 'unsupported_grant_type'}
    Authentication Error. Might be caused by any of the following reasons:
    • Failure to URL Form Encoding the POST request data when exchanging the JWT for an Access Token. Verify that this step is performed, and if so, correctly.
    • The grant_type field is missing from the POST request body or is malformed (it should be Form URL-Encoded). 
    • The Content-Type header is not set to application/x-www-form-urlencoded.
  • [403] {error: 'not allowed'}
    REST API Error. You lack access to support the call you're trying to make. Check that your Service Account has a sufficient Role in the project you wish to authenticate towards.

Further Reading