[PR #9203] fix(two-factor): identity-aware session guard with passkey UV exemption #25400

Open
opened 2026-04-15 22:52:14 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/9203
Author: @gustavovalverde
Created: 4/15/2026
Status: 🔄 Open

Base: mainHead: fix/two-factor-identity-guard


📝 Commits (3)

  • 7fd7ece fix(two-factor): identity-aware session guard with passkey UV exemption
  • 42f784d docs(2fa): use canonical CSRC URL for NIST SP 800-63B-4
  • 01f7e2b fix(two-factor): preserve enforcement on path-less endpoints

📊 Changes

7 files changed (+630 additions, -22 deletions)

View changed files

.changeset/fix-2fa-identity-guard.md (+14 -0)
📝 docs/content/docs/plugins/2fa.mdx (+77 -0)
📝 packages/better-auth/src/plugins/two-factor/constant.ts (+11 -0)
📝 packages/better-auth/src/plugins/two-factor/index.ts (+38 -7)
📝 packages/better-auth/src/plugins/two-factor/two-factor.test.ts (+464 -14)
📝 packages/better-auth/src/plugins/two-factor/types.ts (+21 -1)
📝 packages/passkey/src/routes.ts (+5 -0)

📄 Description

Summary

The 2FA after-hook shipped in v1.6.3 had three behaviors that required a follow-up patch.

Cross-account bypass via identity-blind session guard. The hook exited whenever ctx.context.session was set, regardless of whose session it was. A before-hook that populated ctx.context.session with a session for a different user suppressed the challenge for the authenticated user. The guard now compares user IDs and only exits on same-user rewrites (session refresh, updateUser).

Admin impersonation and multi-session switching regression. /admin/impersonate-user, /admin/stop-impersonating, and /multi-session/* mint sessions as authenticated transitions, not sign-in events. The hook was challenging 2FA against the target user's enrolment, which the operator cannot satisfy. The matcher now skips these path prefixes.

Passkey UX regression. A user-verified passkey already proves possession plus inherence and qualifies as AAL2 under NIST SP 800-63B-4 and FIDO Alliance guidance. Requiring a separate TOTP after UV weakens phishing resistance by reintroducing a manually-entered factor. The passkey plugin now sets ctx.context.passkeyUserVerified when the assertion confirms UV, and the 2FA hook skips the challenge for those sign-ins. The flag lives in a request-scoped context, so it cannot leak into the response body or the session cookie cache.

A new TwoFactorOptions.shouldEnforce(ctx) => boolean | Promise<boolean> option overrides the built-in decision for applications that trust upstream provider MFA on OAuth and SSO callbacks, or that want stricter enforcement. The same-user guard and the session-transition exclusions run before shouldEnforce and cannot be overridden.

Follow-up to #9122.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/9203 **Author:** [@gustavovalverde](https://github.com/gustavovalverde) **Created:** 4/15/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/two-factor-identity-guard` --- ### 📝 Commits (3) - [`7fd7ece`](https://github.com/better-auth/better-auth/commit/7fd7ece08dd6da2f77cf91ebf4a12b3d84f99829) fix(two-factor): identity-aware session guard with passkey UV exemption - [`42f784d`](https://github.com/better-auth/better-auth/commit/42f784d9c19e43f82b3e98b2285a6292768e78ff) docs(2fa): use canonical CSRC URL for NIST SP 800-63B-4 - [`01f7e2b`](https://github.com/better-auth/better-auth/commit/01f7e2b0a78482b9a387b03bcc2b2dbc423bcfcb) fix(two-factor): preserve enforcement on path-less endpoints ### 📊 Changes **7 files changed** (+630 additions, -22 deletions) <details> <summary>View changed files</summary> ➕ `.changeset/fix-2fa-identity-guard.md` (+14 -0) 📝 `docs/content/docs/plugins/2fa.mdx` (+77 -0) 📝 `packages/better-auth/src/plugins/two-factor/constant.ts` (+11 -0) 📝 `packages/better-auth/src/plugins/two-factor/index.ts` (+38 -7) 📝 `packages/better-auth/src/plugins/two-factor/two-factor.test.ts` (+464 -14) 📝 `packages/better-auth/src/plugins/two-factor/types.ts` (+21 -1) 📝 `packages/passkey/src/routes.ts` (+5 -0) </details> ### 📄 Description ## Summary The 2FA after-hook shipped in v1.6.3 had three behaviors that required a follow-up patch. **Cross-account bypass via identity-blind session guard.** The hook exited whenever `ctx.context.session` was set, regardless of whose session it was. A before-hook that populated `ctx.context.session` with a session for a different user suppressed the challenge for the authenticated user. The guard now compares user IDs and only exits on same-user rewrites (session refresh, `updateUser`). **Admin impersonation and multi-session switching regression.** `/admin/impersonate-user`, `/admin/stop-impersonating`, and `/multi-session/*` mint sessions as authenticated transitions, not sign-in events. The hook was challenging 2FA against the target user's enrolment, which the operator cannot satisfy. The matcher now skips these path prefixes. **Passkey UX regression.** A user-verified passkey already proves possession plus inherence and qualifies as AAL2 under NIST SP 800-63B-4 and FIDO Alliance guidance. Requiring a separate TOTP after UV weakens phishing resistance by reintroducing a manually-entered factor. The passkey plugin now sets `ctx.context.passkeyUserVerified` when the assertion confirms UV, and the 2FA hook skips the challenge for those sign-ins. The flag lives in a request-scoped context, so it cannot leak into the response body or the session cookie cache. A new `TwoFactorOptions.shouldEnforce(ctx) => boolean | Promise<boolean>` option overrides the built-in decision for applications that trust upstream provider MFA on OAuth and SSO callbacks, or that want stricter enforcement. The same-user guard and the session-transition exclusions run before `shouldEnforce` and cannot be overridden. Follow-up to #9122. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-15 22:52:14 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#25400