pythonpython-requestspostmanhmac

Authenticating API Request outside of Postman using Python


I am using an API for a portfolio management software and have been provided some documentation on how to implement it through Postman. The results come clean through Postman, but when I try to replicate the script through Python, I am unable to authenticate. I am not well versed in any other language besides Python and I am no API expert (especially when it comes to authentication). I was hoping that someone might be able to look through this code and see if anything seems incorrect with respect to creating a unique signature. Or, if anyone has had experience with creating an HMAC signature using the python requests structure.

import requests
import uuid
import time
import hmac
import base64
import hashlib
import math

url = "url"

payload = "{\n\t\"firm\": \"XXXXXXXX\",\n\t\"id\": \"#######\",\n\t\"data\": {\n\t\t\"tracking_preference\": 2\n\t} \n}\n"

apikey = 'apikey'

uuid = str(uuid.uuid4())
ts = math.floor(time.time())
timestamp = str(ts)

signature = timestamp+uuid

#signature_bytes = signature.encode('UTF8')
#secret_bytes = base64.standard_b64decode(apikey)

signature_bytes = bytes(signature, 'UTF8')
secret_bytes = bytes(apikey, 'UTF8')

signature_hash = hmac.new(secret_bytes, signature_bytes, hashlib.sha256).digest()
hmac = base64.b64encode(signature_hash).decode()


headers = {
    'X-SL-UUID': uuid,
    'X-SL-Timestamp': timestamp,
    'X-SL-HMAC': hmac,
    'Content-Type': "application/json",
    'User-Agent': "PostmanRuntime/7.18.0",
    'Accept': "*/*",
    'Cache-Control': "no-cache",
    'Postman-Token': "unique token",
    'Host': "xxxxxx-xxx.smartleaf.com",
    'Accept-Encoding': "gzip, deflate",
    'Content-Length': "89",
    'Connection': "keep-alive",
    'cache-control': "no-cache"
    }

response = requests.request("POST", url, data=payload, headers=headers)

print(response.text)

Anything with "XXX" or "###" is information I have blurred out. Thanks in advance!

Here is the pre-request script on Postman using JS:

var apikey = '##########';

var uuidlib = require('uuid');
var uuid = uuidlib.v4();

var timestamp = Math.floor(Date.now() / 1000).toString();

var hash = CryptoJS.HmacSHA256(timestamp.concat(uuid), apikey);

postman.setEnvironmentVariable('timestamp', timestamp);
postman.setEnvironmentVariable('uuid', uuid);
postman.setEnvironmentVariable('hmac', hash);

Solution

  • directly translating that Javascript code results in:

    import hmac
    import hashlib
    import time
    import uuid
    import requests
    
    apikey = b'##########'
    url = 'https://xxxxxx-xxx.smartleaf.com/'
    payload = {'firm': 'XXXXXXXX', 'id': '#######', 'data': {'tracking_preference': 2}}
    
    reqid = str(uuid.uuid4())
    reqts = str(int(time.time()))
    
    key = hmac.new(apikey, f'{reqts}{reqid}'.encode('ascii'), hashlib.sha256).hexdigest()
    
    headers = {
        'X-SL-UUID': reqid,
        'X-SL-Timestamp': reqts,
        'X-SL-HMAC': key,
    }
    
    response = requests.post(url, json=payload, headers=headers)
    

    the main this is you were encoding the digest in base64 rather than hexadecimal

    obviously I can't test this, but hopefully it's about right