Backend guides
Two-factor authentication
How authenticator enrollment, sign-in challenges and recovery codes work alongside passkeys.
On this page
Slicekit supports authenticator apps using TOTP (Time-based One-Time Password, RFC 6238) and passkeys. An account has a second factor when its authenticator is confirmed or it has a passkey that counts as a factor. Each method counts only while its feature is enabled. Recovery codes belong to the authenticator; a passkey-only account has none. See Authentication for the password and session flow.
Feature settings
Auth:TotpEnabled controls authenticator endpoints and UI. Disabling it leaves passkeys available
when Auth:PasskeysEnabled is on. Auth:TotpRequired requires enrollment in at least one enabled
second-factor method before ordinary account operations can continue.
"Auth": {
"TotpEnabled": true,
"TotpRequired": false
}
Override settings through environment variables such as Auth__TotpEnabled=false.
GET /api/v1/auth/features reports which methods the deployment offers. The endpoint registration
applies the same gates. Setup routes opt out of the required-enrollment filter so users can add a factor.
Endpoint contracts
Each operation is a vertical slice in Slicekit.Core/Features/Auth/ with a thin endpoint in
Slicekit.Api/Endpoints/v1/. See Adding a vertical slice for that pattern.
The endpoint files and the API’s OpenAPI reference define the current request and response contracts.
Authenticator setup, confirmation, disabling and recovery-code regeneration use the
/api/v1/users/me/totp route family. Sign-in verification and recovery use /api/v1/auth/totp.
Enrollment
The user must be signed in, have a confirmed email, and hold the matching security permissions. Cookie requests also require CSRF protection.
- Begin with
POST /api/v1/users/me/totp/setup, supplying a password or step-up proof. Setup refuses when the authenticator is already enabled, so a session cannot replace a confirmed factor. It returnssecretBase32andqrCodeUrifor the authenticator app; provisioning alone does not enable the factor. - Scan the QR code, then send the first code to
POST /api/v1/users/me/totp/confirm. Confirmation enables the authenticator and returns recovery codes. - Save those recovery codes. They are displayed once, and their plaintext cannot be retrieved later.
The recovery-code dialog must outlive the setup card. Confirmation refreshes the current user and replaces that card, so a dialog inside it would close before the user could save the codes.
Sign-in challenge
When a second factor is required at sign-in, the API returns TotpRequired with a short-lived
pendingToken, totpAvailable and passkeyAvailable. Offer only the methods available to that account.
The pending token cannot authenticate normal API requests.
It binds the account’s credential generation. Password changes, recovery, confirmed email changes and admin
second-factor resets invalidate earlier challenges; the user must start sign-in again.
For an authenticator, send pendingToken and code to POST /api/v1/auth/totp/verify. A successful
verification returns 204 No Content with the session cookies set. Passkey challenges use the
passkey verification flow instead.
Passkey assertions finalize against the latest stored signature counter under a credential row lock across sign-in, second-factor verification and reauthentication. Positive counters must increase, including across concurrent ceremonies. Authenticators that always report zero remain supported.
Recovery and regeneration
If the authenticator is lost, send pendingToken and recoveryCode to
POST /api/v1/auth/totp/recover. Each recovery code works once and is consumed on successful sign-in.
The API stores hashes, with the Identity token column also encrypted at rest.
Signed-in users can replace their recovery-code set through
POST /api/v1/users/me/totp/recovery-codes after step-up verification. Save the new set; regeneration
invalidates the previous codes.
Disabling
DELETE /api/v1/users/me/totp requires a password or single-use step-up proof. It clears the
authenticator key and recovery codes. When second-factor enrollment is mandatory, disabling is refused
if it would remove the account’s last enabled factor. Re-enrollment starts with a new secret.
Throttling
Verification and recovery use the shared auth rate-limit policy and per-account attempt limits:
Auth:MaxTotpAttemptsPerAccountWindow and Auth:MaxRecoveryCodeAttemptsPerAccountWindow.
Exhausting either budget returns a lockout response; successful verification clears its attempt counter.
See Rate limiting for deployment settings.
Checklist
- Set the feature flags and decide whether enrollment is mandatory.
- Complete setup and confirmation, then save the recovery codes before leaving the screen.
- Offer only the challenge methods returned by the sign-in response.
- Verify recovery codes are consumed and regeneration invalidates the old set.
- Require step-up before disabling and preserve the last-factor rule.