[GH-ISSUE #8071] Feature: Passkey re-authentication without session creation (step-up auth) #28309

Open
opened 2026-04-17 19:44:23 -05:00 by GiteaMirror · 0 comments
Owner

Originally created by @ShivamGupta-SM on GitHub (Feb 19, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8071

Problem

The passkey plugin's verifyPasskeyAuthentication endpoint is designed as a login flow — it always creates a new session. There is no way to verify a passkey assertion without creating a session.

This is a blocker for step-up authentication (re-auth) on sensitive operations like:

  • Money transfers / withdrawals
  • Phone number changes
  • Deleting resources
  • Role changes (e.g. promoting to owner)

What happens today

When using auth.api.verifyPasskeyAuthentication() for re-auth:

  1. User already has a valid session
  2. Backend calls verifyPasskeyAuthentication just to confirm biometric ownership
  3. A new orphaned session is created as a side effect
  4. The caller discards the session — only the userId is used for verification

This leads to session table bloat with unused sessions, even with cleanup hooks.

  • #5099 — "Authorised actions with passkey" (closed with "build it yourself")
  • #6000 — "Support PIN-based Step-up Authentication for Sensitive Actions"

The maintainer response on #5099 recommended session freshness, but that's time-based — not a cryptographic proof of user presence. For financial operations, biometric re-auth is the correct pattern.

Proposed solution

Add a verifyOnly mode (or a separate endpoint) that validates the WebAuthn assertion without creating a session.

Option A: Flag on existing endpoint

// Server-side
const result = await auth.api.verifyPasskeyAuthentication({
  body: { response: assertionResponse },
  verifyOnly: true, // NEW — skip session creation
});
// result = { verified: true, userId: "..." }

Option B: Dedicated re-auth endpoint

POST /passkey/verify-assertion

Returns { verified: true, userId: string } without touching the session table.

Option C: Generic step-up auth API

A framework-level concept where any auth method (passkey, TOTP, password) can be used for re-authentication:

const result = await auth.api.verifyStepUp({
  body: { method: "passkey", response: assertionResponse },
});

Current workaround

We use @simplewebauthn/server directly (the same library Better Auth uses internally) to verify assertions without session creation. This works but requires:

  • Manual challenge management (storing/retrieving from the verification table)
  • Manual passkey record lookup
  • Manual counter update (replay protection)
  • Duplicating RP ID / origin config that's already in the passkey plugin

Essentially reimplementing half the passkey plugin just to avoid session creation.

Why this matters

Step-up authentication is a standard pattern in fintech, e-commerce, and any app handling sensitive operations. The passkey plugin is 90% of the way there — it just needs a mode that verifies without creating sessions.

The current gap forces every developer to either:

  1. Accept orphaned sessions (wasteful)
  2. Reimplement WebAuthn verification from scratch (error-prone)

Neither is ideal when the plugin already has all the building blocks internally.

Originally created by @ShivamGupta-SM on GitHub (Feb 19, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8071 ## Problem The passkey plugin's `verifyPasskeyAuthentication` endpoint is designed as a **login** flow — it always creates a new session. There is no way to verify a passkey assertion **without** creating a session. This is a blocker for **step-up authentication** (re-auth) on sensitive operations like: - Money transfers / withdrawals - Phone number changes - Deleting resources - Role changes (e.g. promoting to owner) ### What happens today When using `auth.api.verifyPasskeyAuthentication()` for re-auth: 1. User already has a valid session 2. Backend calls `verifyPasskeyAuthentication` just to confirm biometric ownership 3. A **new orphaned session** is created as a side effect 4. The caller discards the session — only the `userId` is used for verification This leads to session table bloat with unused sessions, even with cleanup hooks. ### Related issues - #5099 — "Authorised actions with passkey" (closed with "build it yourself") - #6000 — "Support PIN-based Step-up Authentication for Sensitive Actions" The maintainer response on #5099 recommended session freshness, but that's **time-based** — not a cryptographic proof of user presence. For financial operations, biometric re-auth is the correct pattern. ## Proposed solution Add a `verifyOnly` mode (or a separate endpoint) that validates the WebAuthn assertion without creating a session. ### Option A: Flag on existing endpoint ```typescript // Server-side const result = await auth.api.verifyPasskeyAuthentication({ body: { response: assertionResponse }, verifyOnly: true, // NEW — skip session creation }); // result = { verified: true, userId: "..." } ``` ### Option B: Dedicated re-auth endpoint ``` POST /passkey/verify-assertion ``` Returns `{ verified: true, userId: string }` without touching the session table. ### Option C: Generic step-up auth API A framework-level concept where any auth method (passkey, TOTP, password) can be used for re-authentication: ```typescript const result = await auth.api.verifyStepUp({ body: { method: "passkey", response: assertionResponse }, }); ``` ## Current workaround We use `@simplewebauthn/server` directly (the same library Better Auth uses internally) to verify assertions without session creation. This works but requires: - Manual challenge management (storing/retrieving from the `verification` table) - Manual passkey record lookup - Manual counter update (replay protection) - Duplicating RP ID / origin config that's already in the passkey plugin Essentially reimplementing half the passkey plugin just to avoid session creation. ## Why this matters Step-up authentication is a standard pattern in fintech, e-commerce, and any app handling sensitive operations. The passkey plugin is 90% of the way there — it just needs a mode that verifies without creating sessions. The current gap forces every developer to either: 1. Accept orphaned sessions (wasteful) 2. Reimplement WebAuthn verification from scratch (error-prone) Neither is ideal when the plugin already has all the building blocks internally.
GiteaMirror added the securitycredentials labels 2026-04-17 19:44:24 -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#28309