Refreshing Access Token
An example of how the authentication access token can be refreshed automatically.

Overview

When running integrations continuously, authentication towards the API must be refreshed periodically. By default, this must be done every hour and should happen automatically without interfering with the integration. In this example, a simple access token refresh routine that keeps the API authenticated has been implemented.

Preliminaries

    Service Account Credentials You must create and know the credentials of a Service Account. Any role will suffice.
    OAuth2 This example heavily builds on the OAuth2 Authentication Guide. It is recommended that you understand this routine before attempting to expand upon it.

Example Code

The following points summarize the provided example code.
    A simulated REST API call is triggered every 5 seconds.
    An instance of the Auth class provides the access token as required.
    If the access token is within 1 minute of expiration, the token is first refreshed.

Environment Setup

If you wish to run the code locally, make sure you have a working runtime environment.
Python 3.9
Node.js 14
The following packages are required by the example code and must be installed.
1
pip install pyjwt==2.1.0
2
pip install requests==2.25.1
Copied!
The following packages are required by the example code and must be added.
1
npm install [email protected]
2
npm install [email protected]
Copied!
Add the following credentials to your environment as they will be used to authenticate the API.
Bash
1
export DT_SERVICE_ACCOUNT_KEY_ID=<YOUR_SERVICE_ACCOUNT_KEY_ID> # [string]
2
export DT_SERVICE_ACCOUNT_EMAIL=<YOUR_SERVICE_ACCOUNT_EMAIL> # [string]
3
export DT_SERVICE_ACCOUNT_SECRET=<YOUR_SERVICE_ACCOUNT_SECRET> # [string]
Copied!

Source

The following code snippet implements the token refresh routine.
Python 3.9
Node.js 14
1
import os
2
import time
3
import jwt # pip install pyjwt==2.1.0
4
import requests # pip install requests==2.25.1
5
6
# Constants for authentication credentials.
7
KEY_ID = os.environ.get('DT_SERVICE_ACCOUNT_KEY_ID', '')
8
SECRET = os.environ.get('DT_SERVICE_ACCOUNT_SECRET', '')
9
EMAIL = os.environ.get('DT_SERVICE_ACCOUNT_EMAIL', '')
10
11
12
class Auth():
13
"""
14
Handles automatic refresh of access token every
15
time get_token() is called with a buffer of 1 minute.
16
17
"""
18
19
refresh_buffer = 60 # s
20
auth_url = 'https://identity.disruptive-technologies.com/oauth2/token'
21
22
def __init__(self, key_id: str, email: str, secret: str):
23
# Set attributes.
24
self.key_id = key_id
25
self.email = email
26
self.secret = secret
27
28
# Initialize some variables.
29
self.token = ''
30
self.expiration = 0
31
32
def refresh(self):
33
# Construct the JWT header.
34
jwt_headers = {
35
'alg': 'HS256',
36
'kid': self.key_id,
37
}
38
39
# Construct the JWT payload.
40
jwt_payload = {
41
'iat': int(time.time()), # current unixtime
42
'exp': int(time.time()) + 3600, # expiration unixtime
43
'aud': self.auth_url,
44
'iss': self.email,
45
}
46
47
# Sign and encode JWT with the secret.
48
encoded_jwt = jwt.encode(
49
payload=jwt_payload,
50
key=self.secret,
51
algorithm='HS256',
52
headers=jwt_headers,
53
)
54
55
# Prepare HTTP POST request data.
56
# note: The requests package applies Form URL-Encoding by default.
57
request_data = {
58
'assertion': encoded_jwt,
59
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer'
60
}
61
62
# Exchange the JWT for an access token.
63
access_token_response = requests.post(
64
url=self.auth_url,
65
headers={'Content-Type': 'application/x-www-form-urlencoded'},
66
data=request_data,
67
)
68
69
# Halt if response contains an error.
70
if access_token_response.status_code != 200:
71
print('Status Code: {}'.format(access_token_response.status_code))
72
print(access_token_response.json())
73
return None
74
75
# Unpack the response dictionary.
76
token_json = access_token_response.json()
77
self.token = token_json['access_token']
78
self.expiration = time.time() + token_json['expires_in']
79
80
def get_token(self):
81
# Check if access token needs a refresh. A 1 minute buffer is added.
82
if self.expiration - time.time() < self.refresh_buffer:
83
# Print expiration message to console.
84
print('Refreshing...')
85
86
# Fetch a brand new access token and expiration.
87
self.refresh()
88
89
# Print time until expiration.
90
print('Token expires in {}s.'.format(
91
int(self.expiration - time.time() - self.refresh_buffer),
92
))
93
94
# Return the token to user.
95
return self.token
96
97
98
def function_that_calls_rest_api(access_token: str):
99
# This would usually do something useful.
100
time.sleep(5)
101
102
103
if __name__ == '__main__':
104
# Initialize an authentication object.
105
auth = Auth(KEY_ID, EMAIL, SECRET)
106
107
# Do some task, here simulated by an infinite loop.
108
while True:
109
# Simulate some routine that needs authentication for the REST API.
110
function_that_calls_rest_api(auth.get_token())
Copied!
1
const jwt = require('jsonwebtoken') // npm install [email protected]
2
const axios = require('axios').default // npm install [email protected]
3
4
// Authentication credentials.
5
const serviceAccountKeyID = process.env.DT_SERVICE_ACCOUNT_KEY_ID
6
const serviceAccountSecret = process.env.DT_SERVICE_ACCOUNT_SECRET
7
const serviceAccountEmail = process.env.DT_SERVICE_ACCOUNT_EMAIL
8
9
// Token refresh variables and constants.
10
const refreshBuffer = 60 // seconds
11
let expiration = 0 // unixtime
12
let accessToken = ''
13
14
async function getAccessToken(keyID, secret, email) {
15
// Check if access token needs a refresh. A buffer is added.
16
if (expiration - Math.floor(Date.now() / 1000) < refreshBuffer) {
17
// Print expiration message to console.
18
console.log('Refreshing...')
19
20
// Fetch a new token response.
21
let res = await refreshAccessToken(keyID, secret, email)
22
23
// Update token and expiration.
24
expiration = Math.floor(Date.now() / 1000) + res.expires_in
25
accessToken = res.access_token
26
}
27
28
// Print time until expiration.
29
let sLeft = expiration - Math.floor(Date.now() / 1000) - refreshBuffer
30
console.log(`Token expires in ${sLeft}s.`)
31
}
32
33
async function refreshAccessToken(keyID, secret, email) {
34
// Construct the JWT header.
35
let jwtHeaders = {
36
'alg': 'HS256',
37
'kid': keyID,
38
}
39
40
// Construct the JWT payload.
41
let jwtPayload = {
42
'iat': Math.floor(Date.now() / 1000), // current unixtime
43
'exp': Math.floor(Date.now() / 1000) + 3600, // expiration unixtime
44
'aud': 'https://identity.disruptive-technologies.com/oauth2/token',
45
'iss': email,
46
}
47
48
// Sign and encode JWT with the secret.
49
const jwtEncoded = jwt.sign(
50
jwtPayload,
51
secret,
52
{
53
header: jwtHeaders,
54
algorithm: 'HS256',
55
},
56
)
57
58
// Prepare POST request data.
59
const requestObject = {
60
'assertion': jwtEncoded,
61
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
62
}
63
64
// Converts the requestObject to a Form URL-Encoded string.
65
const requestData = Object.keys(requestObject).map(function(key) {
66
return encodeURIComponent(key) + '=' + encodeURIComponent(requestObject[key])
67
}).join('&')
68
69
// Exchange JWT for access token.
70
const accessTokenResponse = await axios({
71
method: 'POST',
72
url: 'https://identity.disruptive-technologies.com/oauth2/token',
73
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
74
data: requestData,
75
}).catch(function (error) {
76
// Prints the error response (if any), an re-throws the error.
77
if (error.response) {
78
console.log(error.response.data)
79
}
80
throw error
81
})
82
83
// Return the response data.
84
return accessTokenResponse.data
85
}
86
87
async function functionThatCallsAPI(accessToken) {
88
// Use access token for calling the Rest API here.
89
// We'll just sleep for a few seconds.
90
await new Promise(r => setTimeout(r, 5000));
91
}
92
93
async function main() {
94
// Do some task, here simulated by an infite loop.
95
while (true) {
96
// Call some routine that needs the access token.
97
await functionThatCallsAPI(getAccessToken(
98
serviceAccountKeyID,
99
serviceAccountSecret,
100
serviceAccountEmail,
101
))
102
}
103
}
104
main()
105
Copied!

Expected Output

For visualization purposes, an expiration timer is printed each time the token is used.
1
Refreshing...
2
Token expires in 3538s.
3
Token expires in 3533s.
4
Token expires in 3528s.
5
...
6
Token expires in 13s.
7
Token expires in 8s.
8
Token expires in 3s.
9
Refreshing...
10
Token expires in 3538s.
11
Token expires in 3533s
12
...
Copied!
Last modified 2mo ago