2one
2one

Reputation: 1085

Python connect to API using username, password and API key

I have set up API access to a website and can use their test GUI interface to connect (HTTP 200) using the username, passsword and API key.

If I try code below I get error Response 403:

 from requests.auth import HTTPDigestAuth
 url = 'https://website.com'
 result = requests.get(url, auth=HTTPDigestAuth('username', 'password'))

I think I need to include the API key with the original requests.get but I'm not sure how to do that.

It's a REST API. On the website I generated an API key by providing a Name and then set the API login details (username and password). Under info on the HTTP requests it lists Request Header components:

X-API-KEY: k The API key k (obtained from My Account) is how we identify and authorise the calling application

CST / Authorization A valid access token identifying the client.

X-SECURITY-TOKEN / ACCOUNT-ID A valid account token or account id identifying the client's current account.

Content-Type: application/json Request format type. This should always be set as indicated to json only

Accept: application/json; charset=UTF-8 Response format type. This should always be set as indicated to json only Version:

Version: v API version v (defaults to 1, if not specified)

Is it possible that the API key, username and password are all contained with the header? The username and password are not used to set the API key.

Upvotes: 4

Views: 43717

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1121744

You appear to be using the IG Labs REST Trading API; the documentation states there are two modes of authentication, depending on the API version you specify. The header documentation you quote is a general overview of the different headers that are involved. That section is not very useful to actually understanding how the authentication / authorization works for that API.

On the same page is a Authentication and authorisation section, and it is that section you need to study. This is where the two modes of authentication are described. There is also a separate examples section that explains how the two different modes work with concrete requests and responses shown.

To authenticate, you must first send a POST request to the /session route, with your API key present in the X-IG-API-KEY header. This is where you'll need the username and password. A valid username / password then gives you security tokens to be used for subsequent requests. For API versions 1 and 2 an additional field encryptedPassword can be included to state whether or not the password is plain text or encrypted; you only need this to satisfy IG Singapore login restrictions, and the field can safely be omitted.

For v1 and v2 requests, you get those tokens in the response headers, API version v3 gives you the required information in the body. The tokens have a limited lifetime. The v1/v2 tokens are valid for 6 hours, but are automatically extended up to 72 hours by making requests with those tokens, before you need to log in again. The v3 token is only valid for 60 seconds, but a separate refresh_token lets you get a new token by sending the refresh token to the /session/refresh-token endpoint.

V1 / v2 requests then use the tokens in the exact same headers the /session response uses them, so copy across the CST ("client session token") and the X-SECURITY-TOKEN headers and you are on your way.

V3 requests use the standard OAuth Authorization header, with method set to Bearer. Just construct that header with the access_token value from the oauthToken structure, and save the refresh_token for when the access_token has expired. The documentation also recommends you set the IG-ACCOUNT-ID header, to identify the account (the token identifies the client only).

I strongly recommend you use a requests.Session() object to make header handling simpler.

For versions v1 and v2 of the API, use:

import requests

url = 'https://website.com'
API_KEY = '.... your API key ....'
username = 'username'
password = 'password'

session = requests.Session()
# these are sent along for all requests
session.headers['X-IG-API-KEY'] = API_KEY
# not strictly needed, but the documentation recommends it.
session.headers['Accept'] = "application/json; charset=UTF-8"

# log in first, to get the tokens
response = session.post(
    url + '/session',
    json={'identifier': username, 'password': password},
    headers={'VERSION': '2'},
)
response.raise_for_status()  # if not a 2xx response, raise an exception
# copy across the v1/v2 tokens
session.headers['CST'] = response.headers['CST']
session.headers['X-SECURITY-TOKEN'] = response.headers['X-SECURITY-TOKEN']

Now you are set up to use session to continue to access the API.

For v3, the tokens live in the JSON body:

session = requests.Session()
# these are sent along for all requests
session.headers['X-IG-API-KEY'] = API_KEY

# log in first, to get the tokens
response = session.post(
    url + '/session',
    json={'identifier': username, 'password': password},
    headers={'VERSION': '3'},
)
response.raise_for_status()  # if not a 2xx response, raise an exception
response_data = response.json()
oauth_tokens = response_data['oauthToken']

session.headers['Authorization'] = 'Bearer ' + oauth_tokens['access_token']
session.headers['IG-ACCOUNT-ID'] = response_data['accountId']

Now you are set up to use session to continue to access the API until oauth._tokens['expires_in'] seconds have passed or a 401 response is given:

if response.status == 401 and response.json()['errorCode'] == 'error.security.oauth-token-invalid':
    # refresh required, old token is done.

You need to then use a session.post(url + '/session/refresh-token', json={'refresh_token': oauth_tokens['refresh_token']}) to get a freshoauthToken` structure:

# refresh the access token
del session.headers['Authorization']
response = session.post(
    url + '/session/refresh-token',
    json={'refresh_token': oauth_tokens['refresh_token']},
    headers={'VERSION': '1'},
)
response.raise_for_status()  # if not a 2xx response, raise an exception
oauth_tokens = response.json()

session.headers['Authorization'] = 'Bearer ' + oauth_tokens['access_token']

Note that I keep sending the VERSION header with each individual request; their API uses version numbers per end point, so /session has 3 versions, but /session/refresh-token only has version 1, and you can't set VERSION to anything else or it'll break.

The V3 /session format may appear to be more cumbersome, but that version lets you refresh your access indefinitely, provided you do so within 10 minutes from your last token use; the V1 / V2 access tokens expire after 72 hours tops, whatever you do.

Upvotes: 6

Related Questions