Two-factor authentication
Manage authenticator 2FA for a signed-in user, then sign in with a TOTP or recovery code. Manage paths are under /identity/manage/2fa. POST requires authorization, CSRF (RequestVerificationToken), and ReAuth. Body shape is Identity TwoFactorRequest.
Assume cookie session (credentials: "include") and a fresh CSRF token from GET /identity/csrfToken for manage mutations. Login does not use CSRF.
Video
Read status
- Sign in.
GET /identity/manage/2fa.- Response includes
isTwoFactorEnabled.
Enable
- Complete step-up: Complete step-up (ReAuth) (password, 2FA, or passkey proof).
- Mint or refresh the authenticator shared key with
POST /identity/manage/2faand body{}(plus CSRF). When no key exists, the response includessharedKey. - Show the shared key (or QR) in the authenticator app.
- Complete ReAuth again if the ReAuth cookie expired (default 5 minutes,
ReAuth.Lifetime). POST /identity/manage/2fawith{ "enable": true, "twoFactorCode": "<6-digit TOTP>" }and CSRF.- On
200, storerecoveryCodesfrom the response when present.isTwoFactorEnabledistrue.
Do not send enable: true together with resetSharedKey: true. That combination returns a validation problem.
Disable
- Complete ReAuth.
POST /identity/manage/2fawith{ "enable": false }and CSRF.- On
200,isTwoFactorEnabledisfalse.
To rotate the authenticator key, set resetSharedKey: true (this also disables 2FA until you enable again with a code from the new key).
Optional flags on POST
| Field | Effect |
|---|---|
resetRecoveryCodes | Issues a new set of recovery codes |
forgetMachine | Clears the two-factor remember-client cookie |
Sign in after 2FA is enabled
Cookie facade password login (LoginCookie on POST /identity/login). Send cookies with credentials: "include". Login does not require CSRF. Persistent cookie: ?useSessionCookies=false. See Cookie auth.
Two-step challenge (common SPA path)
- Collect email and password.
POST /identity/loginwith{ "email", "password" }(no 2FA fields).- When 2FA is on and neither code is present, expect 401 Problem details with
titleRequiresTwoFactoranddetailTwo-factor authentication is required.Key the challenge UI offtitle, not arequiresTwoFactorboolean field. - Collect a 6-digit authenticator code, or a recovery code.
- Retry the same
POST /identity/loginwith the same email and password, plus eithertwoFactorCodeortwoFactorRecoveryCode. - On success, expect 200 with an empty body and
Set-Cookiefor the Identity application cookie (default name.AspNetCore.Identity.Applicationunless the host renamed it). Enter the app withcredentials: "include". - On a wrong code, expect 401 with
titleUnauthorizedanddetailInvalid credentials.(same shape as a bad password). Let the user retry or switch to a recovery code.
One round-trip
If the client already has a TOTP or recovery code, send it on the first POST /identity/login with email and password. A valid code succeeds in one request (empty body + application cookie). No prior 401 is required.
Cookie vs JWT detect shape
Cookie POST /identity/login | JWT POST /auth/create | |
|---|---|---|
| 2FA required signal | Problem title: RequiresTwoFactor | Problem extension requiresTwoFactor: true |
| Success | Empty body + application cookie | Access token JSON + refresh cookie |
Same two-step idea; different client detect shape. See JWT.