Duo Security API – How to test

In February 2024, I had to troubleshoot an issue with a Python-based Duo implementation.

There were two things I wanted to check manually, using cURL.

Based on some googling, I came up with the following snippet to check if authenticating to the API worked.

These resources were very helpful:



# Replace this with the variables found in the Duo Admin portal.
# These are generic settings.
integrationKey="xxx"
secretKey="xxx"
apiHostname="xxx.duosecurity.com"

# Endpoint specific.
method="GET"
apiPath="/auth/v2/check"
params="" # Just left this in here, in case parameters do need to be passed.

# Each request needs to be signed.
# On the web, there was a script which used \n for newlines.
# For some reason, it didn't work for me until I actually changed the template to what you see below.
# The template is used to generate a signature, and consists of five things:
# timestamp, method, API hostname, API path and parameters (if there are none, there must be a blank line.)
requestTemplate="$timestamp
$method
$apiHostname
$apiPath
$params"

timestamp=$(date -R)

signature=$(echo -n "$requestTemplate" | openssl sha1 -hmac "$integrationKey" | cut -d" " -f 2)
authHeader=$(echo -n "$integrationKey:$signature" | base64 -w0)

# Some HTTP headers must be set.
curl -s -H "Date: $timestamp" -H "Content-Type: application/x-www-form-urlencoded" -H "Authorization: Basic $authHeader" https://$apiHostname$apiPath

Eventually, it worked and provided me with the output I needed.

Now, to know whether a user is authorized to log in, and (if so) returns the user’s available authentication factors, I wanted to check the /preauth endpoint. The script had to be adjusted a little bit; mainly in the cURL line. We specifically add the method in there (POST); and of course some data needed to be posted (params).

I struggled a while before realizing in this case I should NOT set the Content-Type: application/x-www-form-urlencoded header.



# Replace this with the variables found in the Duo Admin portal.
# These are generic settings.
integrationKey="xxx"
secretKey="xxx"
apiHostname="xxx.duosecurity.com"

# Endpoint specific.
method="GET"
apiPath="/auth/v2/preauth"
params="username=xxx" # Alternative for this endpoint: user_id=xxx where xxx is a Duo user ID.

# Each request needs to be signed.
# On the web, there was a script which used \n for newlines.
# For some reason, it didn't work for me until I actually changed the template to what you see below.
# The template is used to generate a signature, and consists of five things:
# timestamp, method, API hostname, API path and parameters (if there are none, there must be a blank line.)
requestTemplate="$timestamp
$method
$apiHostname
$apiPath
$params"

timestamp=$(date -R)

signature=$(echo -n "$requestTemplate" | openssl sha1 -hmac "$integrationKey" | cut -d" " -f 2)
authHeader=$(echo -n "$integrationKey:$signature" | base64 -w0)

# Some HTTP headers must be set.
curl -s -d "$params" -H "Date: $timestamp" -H "Authorization: Basic $authHeader" -X "$method" https://$apiHostname$apiPath

Since I only wanted to check those two things, I didn’t go through the effort of creating a function for this instead.

Scroll to Top