Skip to main content

Token-based authentication

Data services require specifying authentication headers/tokens to access data. Using Long-lived API tokens can be very convenient, but not secure. Instead of using such tokens, it is recommended to adopt an approach where the application periodically requests a new token from the backend, with a lifespan of 5 to 20 minutes.

Backend side

The backend needs an API that will return a new token, for example, /api/generate-token.

For example, a function for generating API tokens written in JavaScript looks as follows:

const CryptoJS = require('crypto-js')

/**
* @description Generates dxFeed API Self-Signed Token
*
* @param {string} secret - the key used for signature validation, and is provided by dxFeed
* @param {string} issuer - principal that issued this token, and is provided by dxFeed
* @param {string} session - subject of this token, and is provided by dxFeed
* @param {string} message - string with the following format: <user ID>, <filter_1>;…;<filter_n> if specified by dxFeed team.
* @param {number} minutes - lifetime of token in minutes.
*
* NB: when providing `message` parameter, you **must** provide user ID acquired from dxFeed or at least arbitrary string instead of it, e.g.
* 1) if no user ID and no filters were provided by dxFeed, the value of `message` may be "test" or "stub" or any other string;
* 2) if no user ID and one filter "filter" was provided, the value of `message` may be "test,filter" or "stub,filter" or "any_other_string,filter";
* 3) if both user ID ("user_id") and filters ("filter1" and "filter2") were provided, the value of `message` must be "user_id,filter1;filter2".
*
* Usually, in the credentials info provided by dxFeed, if user ID is not set, it's omited from message, so you will see just "message: FILTER".
* This is an invalid "message" value for token generator.
* You need to add an arbitrary string here yourself, so it must become "test,FILTER" to be a valid message.
*/
const createAuthToken = (secret, issuer, session, message, minutes) => {
try {
const notBeforeTime = ''
const expirationDate = new Date()
expirationDate.setMinutes(expirationDate.getMinutes() + minutes)
/**
* Expiration time is set in ms from start of epoch, in UTC
*/
const expirationTime = expirationDate.getTime()
const issuedAtTime = Date.now()

const payload = [issuer, session, notBeforeTime, expirationTime, issuedAtTime, message].join(
','
)

const encodedPayload = Buffer.from(encodeURI(payload)).toString('base64').split('=')[0]

const hash = CryptoJS.HmacSHA256(encodedPayload, encodeURI(secret))

const signature = CryptoJS.enc.Base64.stringify(hash)
.split('=')[0]
.replace(/\+/g, '-')
.replace(/\//g, '_')

return `${encodedPayload}.${signature}`
} catch (err) {
return Promise.reject(err)
}
}
info

A detailed explanation about token format, its usage, and generation for other environments like Java, C#, and Python can be found in the knowledge base's article.

Frontend side

To support refreshable tokens, we need to update the widget initialization. Here is an example using the SimpleChart widget:

let state = undefined

function initializeWidget(token) {
const widget = newSimpleChartWidget({
element: document.getElementById('widget'),
providers: {
feedPath: 'wss://client.dxfeed.com/dxlink-ws',
feedAuthHeader: token,
ipfPath: 'https://client.dxfeed.com/ipf',
ipfAuthHeader: `Bearer ${token}`,
schedulePath: 'https://client.dxfeed.com/schedule',
},
state,
})
widget.addStateListener((newState) => (state = newState))
}

function getToken() {
return fetch('/api/generate-token').then((response) => {
const { token } = response.json()
return token
})
}

/**
* Initial widget render.
* Make an initial token request or obtain it any way suitable for your SSR setup.
*/
void getToken().then((token) => initializeWidget(token))

/**
* Setup a token refresh with 15 min interval.
*/
const tokenUpdateInterval = 900000
setInterval(() => {
void getToken().then((token) => initializeWidget(token))
}, tokenUpdateInterval)
warning

Due to technical limitations of the current API implementation, we need to reinitialize the widget each time we request a new token. Therefore, it's preferable to store the widget state outside the initialization function and pass it back during re-initialization.

Security tips

Mechanisms that will help protect the token-issuing application from DDoS and overuse:

  1. Limit the number of simultaneous connections to the application from one IP address.
  2. Properly configure CORS policies so that only users accessing from the same domain can access the endpoint (i.e., only users of the client's site).
  3. Set rate limits at the proxy and service levels.
  4. Require a Captcha Token and validate it.
  5. Generate a CSRF token for each client and validate it when accessing the endpoint.