Skip to content

feat(mfa): passkey and SSO sign-ins count as MFA - #92

Open
bordoni wants to merge 2 commits into
fix/mfa-securityfrom
feat/mfa-passkey-sso
Open

bordoni wants to merge 2 commits into
fix/mfa-securityfrom
feat/mfa-passkey-sso

Conversation

@bordoni

@bordoni bordoni commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Passkey and SSO sign-ins count as MFA, and the profile's Passkey method finally does something. Stacked on #91. Part of #28.

Passkey and SSO sign-ins skip the code step

WorkOS's authenticate response says how the user signed in (authentication_method). The server reads it from WorkOS itself, so the browser can't set it. Mfa\Policy::satisfied_by_sign_in() decides which methods already include a second factor:

Method Skips the plugin's code step Why
Passkey Always WorkOS only accepts a passkey with user verification (fingerprint, face or PIN), so it's both factors. WorkOS's own MFA counts it the same way
SSO When WorkOS → Settings → Authentication → SSO and Two-Step Verification is on (default; option mfa_trust_sso) The identity provider runs its own MFA. WorkOS's own MFA never applies to SSO users either
Anything else No
  • This applies in every mode, so an enforce: always profile accepts a passkey sign-in, and a required user with no authenticator isn't sent to enroll after one.
  • When the policy would otherwise have asked for a code or an enrollment, the plugin logs mfa.step_skipped and fires workos_mfa_step_skipped.
  • New filters: workos_mfa_trust_sso and workos_mfa_satisfied_by_sign_in, for example to ask one organization's SSO users for a code anyway.

Passkey button

The Login Profile editor offered a Passkey method that nothing implemented. WorkOS runs passkeys only on its hosted sign-in page and has no API for them. A profile with Passkey now shows Sign in with a passkey, which sends the visitor to the hosted page (/auth/oauth/authorize-url?provider=passkey builds a provider=authkit URL). The callback finishes like any other sign-in.

To set it up, turn on Passkeys in the WorkOS dashboard; in production, set up an AuthKit custom domain first. The hosted page offers every method turned on in WorkOS, and a password sign-in there still gets the plugin's code step.

The editor's "WebAuthn / passkey" MFA factor is gone, since WorkOS's MFA API has no such factor.

Tests

  • 8 new tests: passkey skips the step (including for a required user with no factors), SSO is trusted by default and challenged when the setting is off, password/magic/social sign-ins still get the code step, the filter can override, and the passkey authorize URL is built only when the profile offers it.
  • Full suite: 895 tests. The same 11 environment-only failures as main.
  • PHPCS, PHPStan, tsc and the build pass.
  • Live on the iwos110 test site against WorkOS staging, with passkeys and progressive enrollment turned on:
    • The Sign in with a passkey button shows on desktop and mobile and opens WorkOS's hosted page.
    • A password sign-in on the hosted page, with WorkOS's "Create a passkey" prompt accepted, came back to iwos110 and got the plugin's code step. That also covers the hosted-callback resume from feat(mfa): enforce the plugin's MFA policy at sign-in #88 live for the first time.
    • The same user, who has an authenticator app (MFA on in WordPress), then signed in with the passkey and landed in wp-admin with no code step. The passkey lives in Chromium's virtual authenticator, which simulates the fingerprint/PIN check.
  • SSO wasn’t run live (it needs an SSO connection on staging); the unit tests cover it.

A passkey sign-in (WorkOS requires user verification) skips the
plugin's code step, as WorkOS's own MFA does. So does an SSO sign-in,
unless the new 'SSO and Two-Step Verification' setting is turned off
(mfa_trust_sso, on by default). The method comes from WorkOS's
authenticate response (authentication_method). Filters
workos_mfa_trust_sso and workos_mfa_satisfied_by_sign_in, action
workos_mfa_step_skipped, Activity Log event mfa.step_skipped.

The profile's Passkey method did nothing. It now shows a 'Sign in with
a passkey' button that opens WorkOS's hosted sign-in page, where WorkOS
runs passkeys. The unsupported WebAuthn MFA factor leaves the editor.
@bordoni
bordoni added this pull request to stack #87 October 19, 2026 02:14
@bordoni bordoni added this to the 1.1.0 milestone Oct 19, 2026
@bordoni
bordoni marked this pull request as ready for review October 19, 2026 05:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant