Feature: Add duo universal prompt API support#447
Conversation
Newer Duo tenants serve a single-page 'pwl' Universal Prompt keyed by an authkey rather than the older sid-based /frame/v4/* endpoints, which made the client POST to a malformed path and receive HTTP 405 (upstream venth#446). Detect the pwl prompt via its data-authkey element and drive Duo's JSON endpoints under /prompt/{akey}/*: pre_authn/initialization, pre_authn/ evaluation, auth/payload, auth/factors/{push,phone_call}, status polling, and auth/finalize_auth to obtain the OIDC redirect back to ADFS. Surface the verified-push step_up_code when Duo requires it. The legacy sid-based flow is preserved for older tenants.
Broaden the Duo Universal Prompt (pwl) flow to cover Phone Call and Passcode in addition to Duo Push, keeping the existing --duo-factor and --duo-device CLI semantics. - scope device selection to the chosen factor and dedupe by pkey so a device is only offered where it can service the factor; match --duo-device by pkey/name/number with a phoneN ordinal fallback, defaulting to Duo's first device for the factor when nothing matches - drive push and phone_call through one table-driven async helper (POST -> poll -> finalize) and add passcode via the synchronous mobile_otp endpoint - handle both poll response shapes (push nests result as an object, phone_call returns a bare status string) - return a clear error for the still-unsupported WebAuthn factor
Drive Duo's pwl passkey factor directly: GET /auth/factors/passkey/initialization for the credential request options, obtain an assertion from the FIDO2 authenticator, then POST /auth/factors/passkey and finalize. Reuses the existing fido2 client machinery; the pwl submit uses standard base64 for the assertion response fields (unlike the base64url used by the legacy sid flow).
pdecat
left a comment
There was a problem hiding this comment.
AI-assisted review.
The structure's clean — the whole pwl flow is gated behind _is_pwl_prompt() so classic tenants are untouched, the _initiate_authentication 7→8-tuple change has just the one (updated) caller, and the public extract() contract is preserved on every path. Reusing _retrieve_roles_page/roles_assertion_extractor and the table-driven _PWL_ASYNC_FACTORS for push/phone reads well. Confirmed the dropped ClientError/platform imports are unused here (ClientError is still used in _duo_authenticator.py, so the removal's correctly scoped).
Approving — a few non-blocking follow-ups:
rq.get()has no timeout in_pwl_authenticate_webauthn. If every authenticator thread errors before enqueuing (the first failure setscancel, so the rest swallow their exceptions), the CLI hangs forever with no error.rq.get(timeout=...)+ surfacing worker exceptions would turn that into a clean failure.- Dead cancel check: the pwl branch in
extract()testssigned_response == "cancelled", but nothing in the pwl path returns that sentinel — the "no eligible authenticator" case raises instead. Harmless, just never fires. - Push/phone poll caps at ~60s (
range(60)+sleep(1)), which can be tight for reaching for your phone — might be worth bumping. - INFO logging is chatty: full JSON payloads, device names + phone
end_of_numbersuffixes, txids, and the poll body every second land at INFO. Demoting the payload dumps to DEBUG would keep device/phone/session details out of logs.
Classic flow's unaffected either way, so none of these block it.
Payload dumps, device names and phone number suffixes, txids, poll bodies, and result URLs were logged at INFO. Match the classic flow, which keeps such details at DEBUG, so a normal-verbosity run does not surface device/session details.
|
Thanks for the review @pdecat !
Thanks again for the review and with that and approval I think this is good to merge in! |
Fixes #446
Duo rolled out their new single-page Universal Prompt (the "pwl" flow), which talks to a completely different set of JSON endpoints than the prompt aws-adfs already supported. Tenants that got switched over started seeing 405s and could no longer log in.
This adds support for the new prompt alongside the existing one. It detects which prompt Duo serves and drives the pwl endpoints when needed, covering the same factors as before:
--duo-factorand--duo-devicekeep their existing meaning, and the WebAuthn path reuses the FIDO2 code that was already in the file. Verified end to end against a live tenant on the new prompt with all four factors.EDIT: CI/CD has been tested on my fork to be all passing (https://github.com/mehalter/aws-adfs/actions/runs/29338976977)