Sign users in
Pick the sign-in method from the table, then follow its section. The examples use the facade defaults: IdentityPath is /identity, PasskeyPath is /account, Jwt.Path is /auth, and the host maps external sign-in under /auth/external. To pick a stack for your client, see Choose a sign-in stack.
Compare the sign-in methods
| Method | Endpoints | Result | CSRF | Two-factor |
|---|---|---|---|---|
| Password, cookie (facade default) | POST /identity/login | Application cookie, empty 200 body | No | 401 with title RequiresTwoFactor, or send the code in the first request |
| Password, Identity bearer | POST /identity/login, POST /identity/refresh | AccessTokenResponse JSON | No | 401 with title RequiresTwoFactor |
| Password, JWT | POST /auth/create, POST /auth/refresh | { accessToken, tokenType } and an HttpOnly refresh cookie | create: no. refresh: yes. | 401 with the extension requiresTwoFactor: true |
| Passkey | POST /account/passkeys/requestOptions, POST /account/passkeys/login | Cookie (with a query flag), Identity bearer tokens (no flag), or a JWT (JWT completer) | Yes | Skipped |
| GitHub or Google | GET /auth/external/login/{provider}, then the callback | Persistent application cookie, or a JWT refresh cookie | No | Skipped |
Every login request uses the AuthEndpoints.Login rate limit. JWT POST /auth/refresh uses it too. Identity bearer POST /identity/refresh has no rate limit. See Rate-limit policies.
Sign in with a password and a cookie
The cookie facade maps LoginCookie on POST /identity/login. The body is Identity's LoginRequest: { "email", "password", "twoFactorCode"?, "twoFactorRecoveryCode"? }. AuthEndpoints passes email to PasswordSignInAsync as the user name. Registration sets the user name to the email, so the email works.
- Send the credentials with
credentials: 'include'. Login does not need a CSRF token.const response = await fetch('/identity/login', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); - To keep the cookie after the browser closes, add
?useSessionCookies=false.LoginCookiereads onlyuseSessionCookies. It ignoresuseCookies. - Check the response:
200with an empty body and aSet-Cookieheader for.AspNetCore.Identity.Application: the user is signed in.401with titleRequiresTwoFactor: ask for a code. See Enter a two-factor code or a recovery code.401with titleUnauthorizedand detailInvalid credentials.: the password is wrong, the account is locked out, or the email is not confirmed. AuthEndpoints returns one response for all three so that it does not reveal which one applies.429: the rate limit rejected the request.
Sign in with a password and Identity bearer tokens
Use this method for native and mobile clients. Turn it on with AddAuthEndpoints<TUser, TContext>(AuthEndpointsSignIn.IdentityBearer, ...), or compose AddBearerAuthEndpoints and MapBearerAuthEndpoints.
- Send the same
LoginRequestbody toPOST /identity/loginwithout query flags. - On success, read
{ "tokenType", "accessToken", "expiresIn", "refreshToken" }from the200body. Send the access token asAuthorization: Bearer <accessToken>. - To get a new access token, send
{ "refreshToken" }toPOST /identity/refresh.
useCookies=true on this login sets a persistent application cookie instead of returning tokens. useSessionCookies=true sets a session cookie. The 2FA challenge is the same as cookie login: 401 with title RequiresTwoFactor.
/csrfToken route, but passkey ceremonies still need a CSRF token. To use passkeys on a bearer host, map an antiforgery token endpoint yourself. See Quick start.Sign in with a password and a JWT
Turn on JWT with o.Jwt.Enabled = true and o.Jwt.Configure, or compose AddJwtEndpoints and MapJwtAuthEndpoints. Call modelBuilder.UseRefreshToken() on your DbContext and add a migration. See JWT module.
- Send
{ "email", "password", "twoFactorCode"?, "twoFactorRecoveryCode"? }toPOST /auth/createwithcredentials: 'include'. Theemailfield also accepts a user name.const { accessToken } = await fetch('/auth/create', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }).then(r => r.json()); - On success, the
200body is{ "accessToken", "tokenType": "Bearer" }. The response also sets theAuthEndpoints.Jwt.RefreshTokencookie. That cookie is HttpOnly,SameSite=Strict, and lasts 14 days. - To get a new access token, call
POST /auth/refreshwith the cookie and a CSRF token fromGET /auth/csrfToken. The body is{ "accessToken" }, with notokenType.
create returns these errors:
400 { "error": "invalid_request", ... }whenemailorpasswordis missing. This body is not problem details.401with detailInvalid credentials.for a wrong password, a lockout, or an unconfirmed email.401with the extensionrequiresTwoFactor: truewhen 2FA is on and the request has no code.
refresh returns 400 { "errors": [...] } when the cookie is missing, expired, revoked, or reused.
Enter a two-factor code or a recovery code
These steps apply to all three password methods. To turn 2FA on, see Turn on two-factor authentication.
- Send the email and password.
- If the response is the 2FA challenge, ask the user for a 6-digit authenticator code or a recovery code. Cookie and Identity bearer login return title
RequiresTwoFactor. JWTcreatereturns the extensionrequiresTwoFactor: true. - Send the same request again with
twoFactorCodeortwoFactorRecoveryCodeadded.
If the client already has the code, send it in the first request. A valid code succeeds in one round trip.
A wrong code on cookie or Identity bearer login returns 401 with detail Invalid credentials. JWT create returns detail Invalid two factor code. for a wrong authenticator code, and the Identity error description for a wrong recovery code.
Remembered browsers
A persistent cookie login (?useSessionCookies=false) with a valid twoFactorCode sets the Identity two-factor remember-client cookie. Later password logins from that browser skip the 2FA challenge. These logins do not set the cookie:
- A session cookie login (the default).
- A login with
twoFactorRecoveryCode. Each recovery code works once. - JWT
POST /auth/create.
To clear the cookie, send POST /identity/manage/2fa with { "forgetMachine": true }, or call POST /identity/logout.
Sign in with a passkey
Passkeys are on by default. The default completer is IdentityPasskeySignInCompleter. To get a JWT instead, register JwtPasskeySignInCompleter<TUser> with AddPasskeySignInCompleter. Turning on JWT does not select that completer for you.
500. So does any other CSRF-protected endpoint called without a session. To upgrade, run dotnet add package AuthEndpoints --version 3.1.1.Send credentials: 'include' and the CSRF header on both requests. The ceremony state lives in a cookie.
- Get request options from
POST /account/passkeys/requestOptions. Send{}for a discoverable credential, or{ "email" }to sign in identifier-first. An email lets the response reveal throughallowCredentialsthat the account has passkeys. - Call
navigator.credentials.get. If the user cancels, stop. - Send the credential to
POST /account/passkeys/loginwith{ "credentialJson" }.- On the cookie facade, always add
?useSessionCookies=truefor a session cookie or?useCookies=truefor a persistent cookie. These flags follow Identity bearerLogin, notLoginCookie. - With no flag, the default completer returns an Identity bearer
AccessTokenResponse.
const headers = { 'Content-Type': 'application/json', 'RequestVerificationToken': csrfToken }; const options = await fetch('/account/passkeys/requestOptions', { method: 'POST', credentials: 'include', headers, body: '{}' }).then(r => r.json()); const credential = await navigator.credentials.get({ publicKey: PublicKeyCredential.parseRequestOptionsFromJSON(options) }); const response = await fetch('/account/passkeys/login?useSessionCookies=true', { method: 'POST', credentials: 'include', headers, body: JSON.stringify({ credentialJson: JSON.stringify(credential) }) }); - On the cookie facade, always add
- Check the response:
200with an empty body and a cookie: the user is signed in.200with{ "accessToken", "tokenType" }and a refresh cookie: the JWT completer signed the user in.401with detailInvalid credentials.: the account is locked out or the email is not confirmed.400with titleInvalid Credential: the assertion failed orcredentialJsonwas empty.400validation problemInvalidPasskeyState: no ceremony was underway. Start again from step 1.
Sign in with GitHub or Google
Set up the packages and routes as in Register with GitHub or Google. The same callback signs users in.
- Send the browser to the login route with a top-level navigation:
window.location.assign('/auth/external/login/google?returnUrl=' + encodeURIComponent('/dashboard')); - The callback signs in a user who already has this provider login. It refuses a local user with the same email and no link, with the error
auto_link_disabled. WithAutoLinkByEmailset totrue, it links that user only when the provider email is verified and the local email is confirmed. - On success, the response is a
302toreturnUrl:CookieExternalLoginCompleter(the default) sets the application cookie. The cookie is persistent becauseIsPersistentdefaults totrue.JwtExternalLoginCompletersets the JWT refresh cookie. CallPOST /auth/refreshwith a CSRF token to get the access token.
returnUrl must be a rooted local path, or an absolute URL whose origin is in AllowedReturnUrlOrigins. Any other value falls back to DefaultReturnUrl.
Help a user who cannot sign in
- Forgotten password.
POST /identity/forgotPasswordalways returns200. AuthEndpoints sends mail only to a confirmed account. See Reset a forgotten password. - Lost authenticator. The user signs in with a recovery code instead of a TOTP code.
- Locked out. Lockout uses Identity's defaults. Change them with
ConfigureIdentity. See Configuration options. - Unconfirmed email. The user opens the confirmation link first. See Register users.
What sign-in does not support
AuthEndpoints does not include these features:
- Sign-in with a magic link or an emailed one-time code.
- Two-factor codes by SMS or email. Identity has an email token provider, but no endpoint exposes it.
- A passkey as a second factor after a password. Passkeys are a first factor only.
- Built-in Apple or Microsoft providers.
- GitHub or Google sign-in that returns Identity bearer tokens.
- An endpoint that lists or revokes a user's other sessions.
- An admin endpoint that unlocks an account.