Build-an-X workflow for authoring automated tests covering multi-factor authentication flows: TOTP (RFC 6238, deterministic codes from a known secret + fixed time), HOTP (RFC 4226, counter-based), SMS/email OTP, WebAuthn/passkey registration and authentication via Chrome DevTools Protocol virtual authenticator (WebAuthn L2 §11), recovery codes, MFA enrollment, and step-up authentication challenges. Use when the team needs end-to-end MFA test coverage beyond what oauth-flow-test-author covers, or when introducing a new second factor to an existing auth surface.
74
93%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Extends Step 4 of SKILL.md. Reuses the virtual_auth_page fixture defined
there (Playwright + CDP WebAuthn.addVirtualAuthenticator).
WebAuthn Level 2 §7.1 registration: navigator.credentials.create()
with PublicKeyCredentialCreationOptions makes the authenticator mint an
asymmetric key pair; the server verifies the attestation. Authentication (§7.2)
uses navigator.credentials.get() to produce a signed assertion. The CDP
WebAuthn domain provides the virtual authenticator per
WebAuthn L2 §11 ("User Agent Automation").
def test_webauthn_authentication(virtual_auth_page, app_url):
"""Full round-trip: register then authenticate with the same passkey."""
page, cdp, auth_id = virtual_auth_page
# Register first
page.goto(f"{app_url}/settings/passkeys")
page.click("#register-passkey")
page.wait_for_selector("#passkey-registered-confirmation")
# Now authenticate
page.goto(f"{app_url}/login")
page.click("#passkey-login")
page.wait_for_url(f"{app_url}/dashboard")
assert "/dashboard" in page.urldef test_webauthn_user_verification_required(virtual_auth_page, app_url, cdp):
"""When UV is disabled mid-session, server must reject the assertion."""
page, cdp, auth_id = virtual_auth_page
cdp.send("WebAuthn.setUserVerified",
{"authenticatorId": auth_id, "isUserVerified": False})
page.goto(f"{app_url}/login")
page.click("#passkey-login")
page.wait_for_selector("#login-error")
assert page.is_visible("#login-error")Server-side verification uses @simplewebauthn/server
(simplewebauthn.dev/docs/packages/server).
verifyRegistrationResponse({ response, expectedChallenge, expectedOrigin, expectedRPID })
returns { verified, registrationInfo }. After authentication,
verifyAuthenticationResponse({ response, expectedChallenge, expectedOrigin, expectedRPID, credential })
returns { verified, authenticationInfo: { newCounter } } - persist newCounter
to prevent signature-counter replay.