Authorization - All about tokens
Last update June 2020
Operating System Any
Interpreter Python 3.x, Postman

Overview

Refinitiv Data Platform entitlement check is based on OAuth 2.0 specification. As described in the Introduction, the first step of an application work flow is to get a token, which will allow access to the protected resource, i.e. data REST API's. In the next tutorial, we will try to get access token using:

  1. Quickstart and Python tutorials source code
  2. RDP Postman Starter Collection and Tutorial Samples

Token Types

OAuth system defines various mechanisms to get an access token. These mechanisms are called grant type, and can be Authorization Code, Implicit Grant, Resource Owner Password Credentials (ROPC) a.k.a Password, Client Credentials, Device Code or Refresh Token. The highlighted grant types are supported by the Refinitiv Data Platform (RDP). An application can get an access token, initially through Password Grant and later on by using a Refresh Token. The Authorization Code and Implicit Grant types are used for federated authentication in a browser based application. A successful request for token will always return two tokens -

  1. A short lived Access Token, which expires in a few (5) minutes.
    This token is used in every subsequent REST API call until it expires. Access token also have an implicit scope(s), and can only be used for products that it is scoped for. So, the scope of REST API which the user is trying to invoke, must be included when requesting an access token.

  2. A long lived Refresh Token.
    This token is only used to get new Access and Refresh tokens after the old one have expired. It can not be used to access protected resource, i.e. cannot be used with data API's.

A successful authorization call generates new Access and Refresh tokens.

Initially when an application does not have any token, it must use a username/password combination to get the first set of tokens. This type of request is called Resource Owner Password Credentials or Password Grant in short. Once an application has a set of tokens, it should store the Refresh token in a persistent storage and use the Access token to get data. Upon expiry of Access token, the Refresh token must then be used to get new Access and Refresh tokens. This use of Refresh token to get subsequent tokens, avoids having to send password across the network repeatedly.

Underlying Mechanics

We will briefly discuss the HTTP requests/response for authorization. For Refinitiv Data Platform, the token endpoint is https://api.refinitiv.com/auth/oauth2/VERSION/token, where the <VERSION> number will change, as the API evolves.

There are few additional parameters which are required by token API, for compliance with OAuth 2.0 specification:

Parameter Description
Grant Type Are you requesting new tokens using username/password combination (password), or through an existing refresh token (refresh_token).
Username The username, used for "Password Grant". This is typically an email address, or a Machine ID which has been assigned to you.
Password Password associated with the username above. This field may contain special characters or reserved characters which may have to be escaped.  The applications must be able to handle special characters.
Client ID Aka Application ID, aka AppKey is a unique identifier, defined for the application or the user making a request. A user can generate/manage their Application ID's using the  AppkeyGenerator. The client ID parameter can be passed in the request body or as "Authorization" request header which is encoded as base64. This Authorization header method is used in the interactions below.
Client Secret Part of OAuth specification  , this parameter is not used in RDP.
Token Scope A user can optionally limit the scope of generated token, so that Access token is valid only for a specific data set. See Token Scopes for explanation.

For additional information on request parameters, please see the API documentation.

A successful authentication response from server contains following parameters:

Parameter Description
access_token The token used to invoke REST data API calls as described above
refresh_token Refresh token to use used for getting next access token
expires_in Access token validity time in seconds
scope A list of all the scopes this token can be used with
token_type "Bearer" - i.e, RDP token service has generated and sent us the Access token in the response message

Below we describe, how the interaction would take place in HTTPS methods.

Password Grant

This is a Request using Password Grant, where user is logging in with their credentials. Note that for a very first time login, when a password needs to be changed, newPassword should be included in the request as well.

    	
            

POST https://api.refinitiv.com/auth/oauth2/v1/token HTTP/1.1

 

Host: api.refinitiv.com

User-Agent: python-requests/2.12.4

Accept-Encoding: gzip, deflate

Accept: application/json

Connection: keep-alive

Content-Length: 112

Content-Type: application/x-www-form-urlencoded

Authorization: Basic RDZENz*************DNkNEREQ3NDo=

 

username=****&password=****&grant_type=password&scope=trapi

Also note that the client ID has been sent in the HTTP header as Basic Authentication Authorization: Basic RDZENz*************DNkNEREQ3NDo=. This could have been included in the request body as well. If our credentials were valid, the server will respond with a message:

    	
            

HTTP/1.1 200 OK

 

Date: Thu, 05 Apr 2018 18:57:06 GMT

Content-Type: application/json

Content-Length: 1604

Connection: keep-alive

X-Tr-Requestid: dd6e2131-917d-4cd8-856e-3754ff6a6990

 

{

    "access_token":"****",

    "refresh_token":"****",

    "expires_in":"300" ,

    "scope":"trapi.alerts.news.crud .... trapi.streaming.pricing.read",

    "token_type":"Bearer"

}

The response body is a JSON message (pretty printed here for explanation), which contains the Access and Refresh tokens as described above. At this point the Refresh token should be stored for future use.

Using the token

The Access token which we just received can now be used in the REST API data calls. Typically this token is sent as a Bearer in the HTTP Authorization header in the request message. If the user tries to invoke an API call using an expired token, server will reject the call. Here is an example of a data request and a rejected response:

    	
            

GET https://api.refinitiv.com/data/historical-pricing/v1/views/interday-summaries/TRI.N HTTP/1.1

 

Host: api.refinitiv.com

User-Agent: python-requests/2.12.4

Accept-Encoding: gzip, deflate

Accept: */*

Connection: keep-alive

Authorization: Bearer ****

Response:

    	
            

HTTP/1.1 401 Unauthorized

 

Date: Wed, 18 Dec 2019 14:45:27 GMT

Content-Type: application/json

Content-Length: 119

Connection: keep-alive

Access-Control-Allow-Origin: *

Www-Authenticate: Bearer realm="GET /data/historical-pricing/v1/views/interday-summaries/{universe}", scope="trapi.data.historical-pricing.summaries.read", error="invalid_token", error_description="token expired"

X-Tr-Requestid: 33603908-52b2-4e50-ba30-5336d116f50a

 

{

"error": {

    "id":"33603908-52b2-4e50-ba30-5336d116f50a",

    "code":"401",

    "message":"token expired",

    "status":"Unauthorized"

    }

}

The server rejected our data API call with the message "token expired". Typically, an application would keep track of when an Access token is about to expire, and renew it before invoking a data call.

Refreshing the token

Since the previous token is no longer valid, the user/application has to get a new Access token, using the Refresh Grant:

    	
            

POST https://api.refinitiv.com/auth/oauth2/v1/token

 

Host: api.refinitiv.com

User-Agent: python-requests/2.12.4

Accept-Encoding: gzip, deflate

Accept: application/json

Connection: keep-alive

Content-Length: 106

Content-Type: application/x-www-form-urlencoded

Authorization: Basic RDZENz*************DNkNEREQ3NDo=

 

refresh_token=****&grant_type=refresh_token

Response:

    	
            

HTTP/1.1 200 OK

 

Date: Thu, 05 Apr 2018 19:24:02 GMT

Content-Type: application/json

Content-Length: 1604

Connection: keep-alive

X-Tr-Requestid: 4cbb7aaa-9492-499c-82f0-efd0f07bbf14

 

{

    "access_token":"****",

    "refresh_token":"****",

    "expires_in":"300" ,

    "scope":"trapi.alerts.news.crud .... trapi.streaming.pricing.read",

    "token_type":"Bearer"

}

Password change and Take exclusive signon control

Following optional parameters can also be used in the request for a new token using Password Grant:

Parameter Description
takeExclusiveSignOnControl  If set to true, force sign-out of other applications using the same credentials (invalidates refresh token of other applications)
newPassword Change the password associated with this username. Both current and new passwords will be required in order to authenticate and change password.

The parameter, takeExclusiveSignOnControl, may be set to true ONLY if application sending authorization request needs all other sessions/applications to be logged out. Here are a couple of use cases when takeExclusiveSignOnControl must be set to true:

  1. Refresh token has been lost or invalid resulting in errors like:  {"error":"access_denied" ,"error_description":"Session quota is reached." }
  2. Password must be changed

In this scenario, a user may opt to force logout, all other signed-in instances, and request a new set of tokens. A request with an additional flag takeExclusiveSignOnControl will get fulfilled in that case:

    	
            

POST https://api.refinitiv.com/auth/oauth2/v1/token HTTP/1.1

 

Host: api.refinitiv.com

User-Agent: python-requests/2.12.4

Accept-Encoding: gzip, deflate

Accept: application/json

Connection: keep-alive

Content-Length: 112

Content-Type: application/x-www-form-urlencoded

Authorization: Basic RDZENz*************DNkNEREQ3NDo=

 

username=****&password=****&grant_type=password&scope=trapi&takeExclusiveSignOnControl=true

Similarly, if the user intends to change their password, the request message body will contain a string like: username=****&password=oldPassword&grant_type=password&scope=trapi&newPassword=****

AnchorToken Scopes

Every resource (data REST API) within RDP has a permission set assigned to it. This permission set is called scope. The Access token must include the scope of the resource it is trying to access, otherwise the request will be rejected by platform.

For e.g. a timeseries REST API has a scope of trapi.data.historical-pricing.read. The Access token must include trapi.data.historical-pricing.read in addition to other scopes, to get timeseries data. This scope limiting is done when requesting the original token using Password Grant. The request parameter: Scope can contain either a root scope or a subset. Requesting the top level scope trapi will get us a token, which contains all the scopes we are entitled to. Similarly, requesting for trapi.cfs.publisher will get a token, which is entitled to Read: trapi.cfs.publisher.read and Write: trapi.cfs.publisher.write scopes.

Most applications will request a token with root scope: trapi. Following are the list of scopes defined within the Refinitiv Data Platform, and may optionally be used when requesting token:

  • trapi.data.api.test
  • trapi.data.esg.read
  • trapi.data.esg.metadata.read
  • trapi.data.esg.universe.read
  • trapi.data.esg.views-basic.read
  • trapi.data.esg.views-scores.read
  • trapi.data.esg.views-scores-standard.read
  • trapi.data.esg.views-scores-full.read
  • trapi.data.esg.views-measures.read
  • trapi.data.esg.views-measures-standard.read
  • trapi.data.esg.views-measures-full.read
  • trapi.data.esg.bulk.read
  • trapi.data.symbology.read
  • trapi.data.symbology.bulk.read
  • trapi.data.research.read
  • trapi.alerts.research.crud
  • trapi.alerts.news.crud
  • trapi.auth.cloud-credentials
  • trapi.cfs.publisher.read
  • trapi.cfs.publisher.write
  • trapi.cfs.publisher.setup.write
  • trapi.cfs.publisher.stream.write
  • trapi.cfs.subscriber.read
  • trapi.cfs.claimcheck.write
  • trapi.cfs.claimcheck.read
  • trapi.data.news.read
  • trapi.data.pricing.read
  • trapi.streaming.pricing.read
  • trapi.data.historical-pricing.read
  • trapi.data.quantitative-analytics.read