Before You Start
- An integration key, a secret key and the API hostname from the operator (how to get them).
- A WiraPass account to test with: install WiraPass on a phone, sign in with your email and turn on approvals in the Approvals tab.
- An accurate server clock (NTP). Signatures are refused when your clock is more than 5 minutes off.
The Flow
POST /auth/v2/preauthwithusernameset to the user's WiraPass email. Continue only whenresultisauth.POST /auth/v2/authwithfactor=push,device=auto,async=1. The answer is atxid.GET /auth/v2/auth_status?txid=…again and again while result is waiting. Each call waits up to 8 seconds, so no pause is needed in between.allow: let the user in.deny: refuse. If status isfraud, the user pressed Not me: alert your administrators.
Python with Duo's Official Library
The duo_client library works as it is: pip install duo_client.
import duo_client
auth = duo_client.Auth(
ikey="DIXXXXXXXXXXXXXXXXXX",
skey="your-secret-key-from-the-operator",
host="auth.wiracode.com",
)
USERNAME = "rina@example.com" # the user's WiraPass email
pre = auth.preauth(username=USERNAME, ipaddr="203.0.113.7")
if pre["result"] == "auth":
# Send an approval request to the user's phone; do not block.
txid = auth.auth(
factor="push",
username=USERNAME,
device="auto",
async_txn=True,
type="Sign in to the Staff Portal",
ipaddr="203.0.113.7",
)["txid"]
# auth_status long-polls: each call waits up to 8 seconds for a change.
status = auth.auth_status(txid)
while status["waiting"]:
status = auth.auth_status(txid)
if status["success"]:
print("allow: access granted")
else:
print("deny:", status["status"], status["status_msg"])
else:
# "enroll": the user has no approval phone yet; "deny": paused after "Not me".
print(pre["result"] + ":", pre["status_msg"])
Node.js Without Libraries
This example uses duo.mjs from Signing Requests.
import { duoCall } from './duo.mjs';
const username = 'rina@example.com'; // the user's WiraPass email
const pre = (await duoCall('POST', '/auth/v2/preauth', { username })).response;
if (pre.result === 'auth') {
const { txid } = (await duoCall('POST', '/auth/v2/auth', {
username, factor: 'push', device: 'auto', async: '1', type: 'Sign in to the Staff Portal',
})).response;
let status;
do {
// Long-poll: each call waits up to 8 seconds for a change.
status = (await duoCall('GET', '/auth/v2/auth_status', { txid })).response;
} while (status.result === 'waiting');
console.log(status.result === 'allow' ? 'allow: access granted' : `deny: ${status.status} ${status.status_msg}`);
} else {
console.log(`${pre.result}: ${pre.status_msg}`);
}
When the Phone Cannot Be Reached
When no approval phone is signed in to WiraPass, preauth still answers auth, but status_msg suggests the backup code, and a push is answered deny at once without sending anything. Ask the user for the backup code from the WiraPass app:
result = auth.auth(factor="passcode", username=USERNAME, passcode=code_typed_by_user)
if result["result"] == "allow":
print("allow: access granted")Waiting for the Answer in One Call
Without async, the auth call waits until the user answers or it times out (60 seconds), then gives the final result and status. Set your HTTP client timeout above 65 seconds. If the client hangs up first, the request on the phone is cancelled.