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.
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/optionsandPOST /auth/reauth/passkeyrun a WebAuthn assertion for the current user (the same machinery as the passkey 2FA flow) and mint a proof.GET /auth/reauth/{provider}/startandPOST /auth/reauth/{provider}/callbackrun 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.