Getting 40103 on making GET REST API calls
Nirmalendu opened this issue · 5 comments
The provided Python client works fine and gives expected outcome. However, when I try to build a standalone Python file based on the API documentation I get 40103
Description
I followed the API documentation at https://duo.com/docs/adminapi#api-clients.
I see in the Python client, the connection isn't a simple request.get(uri, header) but involves passing a certificate and specifying the port numbers. Since, there is no separate repo for the REST API, I am sharing my findings here.
The API I am calling is GET /admin/v2/logs/authentication'
Expected Behavior
200 OK { "response": { "authlogs": [ { "access_device": { "browser": "Chrome Mobile", "browser_version": "120.0.0.0", "epkey": null, "flash_version": null, "hostname": null, "ip": "XXXXXXXXXXX, "is_encryption_enabled": "unknown", "is_firewall_enabled": "unknown", "is_password_set": "unknown", "java_version": null, "location": { "city": "XXXXXXXXXXX", "country": "XXXXXXXXXXX", "state": "XXXXXXXXXXX" }, "os": "Android", "os_version": "10" }, "adaptive_trust_assessments": {}, "alias": "unknown", "application": { "key": "XXXXXXXXXXX", "name": "User Portal" }, "auth_device": { "ip": null, "key": "XXXXXXXXXXX", "location": { "city": null, "country": null, "state": null }, "name": "XXXXXXXXXXX" }, "email": null, "event_type": "enrollment", "factor": "Platform authenticator (2fa)", "isotimestamp": "2023-12-16T06:45:29.609614+00:00", "ood_software": null, "reason": null, "result": "success", "timestamp": 1702709129, "trusted_endpoint_status": "unknown", "txid": "00fe2487-be6a-4d2d-905b-bb7aa9956353", "user": { "groups": [], "key": "XXXXXXXXXXXXXX", "name": "XXXXXXXXXXX@gmail.com" } } ], "metadata": { "next_offset": [ "1702709129609", "00fe2487-be6a-4d2d-905b-bb7aa9956353" ], "total_objects": 1 } }, "stat": "OK" }
Actual Behavior
Error: 401 - {"code": 40103, "message": "Invalid signature in request credentials", "stat": "FAIL"}
Steps to Reproduce
- Follow the documentation at https://duo.com/docs/adminapi#api-clients to create the authorization header
- Do a 'response = requests.get(url=url, headers=headers)'
https://help.duo.com/s/article/1338?language=en_US
40103 means the signing signature is not correct.
You could follow our doc on how we do authentication for api request https://duo.com/docs/adminapi#authentication
Or if you want to follow our code example, this is where the authentication happens in the client. https://github.com/duosecurity/duo_client_python/blob/master/duo_client/client.py#L113
I followed the doc, also confirmed the signature. I still get the same error. I see that in client.py the request happens after CertValidatingHTTPSConnection. Is passing the certificate mandatory? Consider the case when a user wants to try out the APIs on Postman/Fiddler. How will we do that?
I see that in client.py the request happens after CertValidatingHTTPSConnection. Is passing the certificate mandatory?
Not necessarily, in the elif block above it you can see an example on how you can make request without pass in any certs
This is the code I am using, please let me know if I followed the steps wrong:
def main():
#define variables
ikey='XXXXXX'
skey='XXXXXX'
host='api-dd23b954.duosecurity.com'
path='/admin/v2/logs/authentication'
method='GET'
params={}
params['mintime']='1702615541159'
params['maxtime']='1702715541159'
#create headers
headers = sign(method=method, host=host, path=path, params=params, skey=skey, ikey=ikey)
#create token
#add content headers
#make uri
url = 'https://' + 'api-dd23b954.duosecurity.com' + '/admin/v2/logs/authentication'
#make request
headers['Host'] = host.encode('utf-8')
#headers['Content-Length']='35'.encode('utf-8')
#headers['Content-Type']='application/x-www-form-urlencoded'
response = requests.get(url=url, headers=headers)
if response.status_code == 200:
# Print the response content (usually JSON data for APIs)
print(response.json())
else:
# Print an error message if the request was not successful
print(f"Error: {response.status_code} - {response.text}")
def sign(method, host, path, params, skey, ikey):
"""
Return HTTP Basic Authentication ("Authorization" and "Date") headers.
method, host, path: strings from request
params: dict of request parameters
skey: secret key
ikey: integration key
"""
# create canonical string
now = email.utils.formatdate()
canon = [now, method.upper(), host.lower(), path]
args = []
for key in sorted(params.keys()):
val = params[key].encode("utf-8")
args.append(
'%s=%s' % (urllib.parse.
quote(key, '~'), urllib.parse.quote(val, '~')))
canon.append('&'.join(args))
canon = '\n'.join(canon)
# sign canonical string
sig = hmac.new(bytes(skey, encoding='utf-8'),
bytes(canon, encoding='utf-8'),
hashlib.sha512)
auth = '%s:%s' % (ikey, sig.hexdigest())
# return headers
return {'Date': now, 'Authorization': 'Basic %s' % base64.b64encode(bytes(auth, encoding="utf-8")).decode()}
I have tried with sha1 as mentioned in the docs and sha512 as used in the python client. I keep getting 40103.
Thank you for your help.
@Nirmalendu the query parameters need to be included in the URL:
url = 'https://' + 'api-dd23b954.duosecurity.com' + '/admin/v2/logs/authentication'
url += f'?mintime={params["mintime"]}&maxtime={params["maxtime"]}'
After making that change, your script works for me. That should get it working for you!
For future reference, note that in general contacting Duo Support or posting in the Duo Community Forums are more appropriate avenues for help with application development.