feat(webauthn): add mfa option to passkey enrollment (update)#1594
Conversation
Add an optional login_options to webauthn update_start (sync + async) so enrolling a passkey for an already-authenticated user can return a single session whose amr merges the previously-passed factors with the new passkey, instead of a separate sign-in. update_finish now returns that JWT response (nested under "jwt") when present, else None. Pairs with descope/backend webauthn add-device mfa support (descope/etc#16573). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
🐕 Review complete — View session on Shuni Portal 🐾 |
🐕 Suggested ReviewersThe reviewers were selected based on recent contributions to the key files involved in WebAuthn implementation and testing, ensuring they have relevant and current context to review the changes effectively.
Suggested by Shuni based on git history and PR context. Names are not @-mentioned to avoid notifying anyone — request a review from whoever fits best. |
There was a problem hiding this comment.
🐕 Shuni's Review
Adds an optional login_options to the WebAuthn passkey-enrollment flow so update_finish can return a merged-amr session in one ceremony (sync + async).
No issues found — good bones! Serialization (login_options.__dict__), the sync/async generate_jwt_response/prepare_jwt_response split, and the optional-param backward compat all match the sibling sign-in/sign-up flows. Tests cover the mfa and default paths in both modes. Woof!
Coverage reportThe coverage rate went from
Diff Coverage details (click to unfold)descope/authmethod/webauthn.py
descope/authmethod/_webauthn_base.py
descope/authmethod/webauthn_async.py
|
Fixes the Lint & Type Check (ruff format --check) CI failure. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the OTP update phone/email API: webauthn update_start (sync + async) takes an optional mfa bool (sent as the 'mfa' request field) instead of a LoginOptions, mirroring the backend change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Adds an optional
login_optionsto the WebAuthn passkey-enrollment flow (sync + async) so it can return a merged-amrsession in a single ceremony:Why
Enrolling a passkey for an already-authenticated user registered the credential but minted no session — getting a session reflecting the new passkey required a second WebAuthn ceremony (
sign_in_start(mfa=True)+ finish). Reported by PerciHealth (descope/etc#16573); pairs with the backend change.Changes
update_start(syncwebauthn.py+ asyncwebauthn_async.py) gains an optional trailinglogin_options: Optional[LoginOptions] = None(non-breaking), composed into the request body only when provided (shared_webauthn_base._compose_update_start_body).update_finishgains an optionalaudiencekwarg and now returnsOptional[dict]: the JWT response (parsed from the nestedjwt) when the enrollment was mfa, elseNone(default flow). Sync usesgenerate_jwt_response; async usesprepare_jwt_response.Both changes are runtime backward-compatible (new optional params;
Nonereturn for the default flow).Tests
test_compose_update_start_body(mfa body),test_update_start_with_mfa,test_update_finish_with_mfa, plus the existing default-flow tests — all run in both sync and async modes.tests/test_webauthn.pypasses (25 cases).🤖 Generated with Claude Code