Changelog
Examples

Two-factor authentication

Enable and disable authenticator 2FA, then sign in with a TOTP or recovery code.

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

  1. Sign in.
  2. GET /identity/manage/2fa.
  3. Response includes isTwoFactorEnabled.

Enable

  1. Complete step-up: Complete step-up (ReAuth) (password, 2FA, or passkey proof).
  2. Mint or refresh the authenticator shared key with POST /identity/manage/2fa and body {} (plus CSRF). When no key exists, the response includes sharedKey.
  3. Show the shared key (or QR) in the authenticator app.
  4. Complete ReAuth again if the ReAuth cookie expired (default 5 minutes, ReAuth.Lifetime).
  5. POST /identity/manage/2fa with { "enable": true, "twoFactorCode": "<6-digit TOTP>" } and CSRF.
  6. On 200, store recoveryCodes from the response when present. isTwoFactorEnabled is true.

Do not send enable: true together with resetSharedKey: true. That combination returns a validation problem.

Disable

  1. Complete ReAuth.
  2. POST /identity/manage/2fa with { "enable": false } and CSRF.
  3. On 200, isTwoFactorEnabled is false.

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

FieldEffect
resetRecoveryCodesIssues a new set of recovery codes
forgetMachineClears 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)

  1. Collect email and password.
  2. POST /identity/login with { "email", "password" } (no 2FA fields).
  3. When 2FA is on and neither code is present, expect 401 Problem details with title RequiresTwoFactor and detail Two-factor authentication is required. Key the challenge UI off title, not a requiresTwoFactor boolean field.
  4. Collect a 6-digit authenticator code, or a recovery code.
  5. Retry the same POST /identity/login with the same email and password, plus either twoFactorCode or twoFactorRecoveryCode.
  6. On success, expect 200 with an empty body and Set-Cookie for the Identity application cookie (default name .AspNetCore.Identity.Application unless the host renamed it). Enter the app with credentials: "include".
  7. On a wrong code, expect 401 with title Unauthorized and detail Invalid 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 POST /identity/loginJWT POST /auth/create
2FA required signalProblem title: RequiresTwoFactorProblem extension requiresTwoFactor: true
SuccessEmpty body + application cookieAccess token JSON + refresh cookie

Same two-step idea; different client detect shape. See JWT.