Skip to content
Slicekit

Backend guides

Step-up re-authentication

Let passwordless OAuth users confirm sensitive actions with a passkey or a fresh OAuth round-trip instead of a password.

View .md
On this page

Some actions are sensitive enough to re-check who is at the keyboard: deleting an account, changing the email, disabling two-factor, or setting a password. Slicekit asks the user to re-authenticate for these, and accepts the result in place of the normal password prompt. Users who signed up with a password just type it. Users who signed in with an OAuth provider have no password, so they re-authenticate with a passkey or a fresh OAuth round-trip instead.

The proof

A re-authentication produces a short-lived, single-use proof: a token stored in the cache for five minutes, keyed by both the user id and the token so it can never be replayed for another account. The proof is minted by the re-auth endpoints and consumed by the sensitive operation. It lives in Slicekit.Core.Auth (outside the feature slices) because several features depend on it.

Re-auth endpoints

All are authenticated, CSRF-protected and rate-limited, and they return { reauthToken }.

  • POST /auth/reauth/passkey/options and POST /auth/reauth/passkey run a WebAuthn assertion for the current user (the same machinery as the passkey 2FA flow) and mint a proof.
  • GET /auth/reauth/{provider}/start and POST /auth/reauth/{provider}/callback run an OAuth round-trip, verify the returned identity is already linked to the current user, and mint a proof. They reuse the login redirect URI, with their own cache namespace and cookie so a re-auth can never be mistaken for a login.

Password or proof

The sensitive operations (DELETE /users/me, POST /users/me/request-email-change, DELETE /users/me/totp, and POST /users/me/change-password) accept an optional re-auth token next to the password field. Each handler applies one rule: if the user has a password, verify the password; if not, verify and consume the proof. Change-password doubles as set a password for passwordless users, so an OAuth-only account can add password sign-in after proving itself. GET /users/me exposes a hasPassword flag so the UI knows which path to show.

On the frontend

The reauth feature slice provides a StepUpVerify component that offers “Verify with passkey” (inline) and “Continue with <provider>”. The OAuth option is a full-page redirect in the same tab (not a popup, which providers break with their Cross-Origin-Opener-Policy): the pending operation is stashed in sessionStorage, the OAuth callback completes the re-auth, and the originating form resumes on return. Password-based forms are unchanged; the step-up UI appears only when hasPassword is false.

Live demo

Explore the running app

No sign-up. Pick an account to open the demo with its credentials prefilled, or open it and sign in yourself.

Open without signing in

Credentials are prefilled. Shared password Slicekit-Demo-2026!.