Changelog
Concepts

MapIdentityApi alternative

MapIdentityApi alternative for ASP.NET Core when you need logout, chosen routes, JWT, or passkey endpoints.

MapIdentityApi<TUser>() is ASP.NET Core's Identity API. One call maps register, login, refresh, email confirmation, password reset, and account management onto your Identity user store. AuthEndpoints uses that same store. This page lists where MapIdentityApi stops in .NET 10, and which AuthEndpoints call covers each gap.

Register, login, password reset, and two-factor authentication, route by route: Stock Identity endpoints vs AuthEndpoints.

What MapIdentityApi maps

In .NET 10, MapIdentityApi<TUser>() maps this set. Paths sit at the app root unless you wrap the call in MapGroup.

MethodPath
POST/register
POST/login
POST/refresh
GET/confirmEmail
POST/resendConfirmationEmail
POST/forgotPassword
POST/resetPassword
POST/manage/2fa
GET/manage/info
POST/manage/info
builder.Services
    .AddIdentityApiEndpoints<AppUser>()
    .AddEntityFrameworkStores<AppDbContext>();

app.MapGroup("/identity").MapIdentityApi<AppUser>();

POST /login uses the Identity bearer scheme unless the query string sets useCookies or useSessionCookies, which issue the application cookie instead. Request bodies are the Identity types (RegisterRequest, LoginRequest, and the rest). There is no GET /manage/2fa. The method's documentation comment mentions logging out. The method does not map a logout route.

Logout

MapIdentityApi has no logout route. The Identity API authorization article tells you to add your own POST /logout that calls SignInManager.SignOutAsync(). That signs out the application scheme, plus the external and two-factor user-id schemes when they are registered. It does not sign out the two-factor remember-client cookie. It does not revoke bearer tokens. The bearer handler treats sign-out as a no-op, so the access token and the refresh token stay valid until they expire.

AuthEndpoints maps a logout route on each sign-in stack. Each stack ends a different thing. Client steps: Sign users out.

StackRouteWhat logout ends
Cookie (facade default)POST /identity/logoutApplication, external, two-factor user-id, remember-client, and ReAuth cookies. Requires a signed-in user and a CSRF token.
Identity bearerPOST /identity/logoutThose same cookies. Bearer access and refresh tokens stay valid until they expire. Requires a signed-in user. No CSRF token.
JWTPOST /auth/logoutThe refresh-token family for the refresh cookie, and the cookie itself. The access JWT stays valid until it expires (15 minutes by default). CSRF token required. A signed-in user is not required.

Cookie logout:

const { csrfToken } = await fetch('/identity/csrfToken', { credentials: 'include' }).then(r => r.json());
await fetch('/identity/logout', {
  method: 'POST',
  credentials: 'include',
  headers: { 'RequestVerificationToken': csrfToken }
});

GET /identity/csrfToken is part of the cookie module. MapIdentityApi does not map it.

Choose which endpoints to map

MapIdentityApi maps the full table above in one call. MapGroup("/identity") prefixes every route with /identity. The builder it returns can add conventions to that group. It has no parameter that omits /register, and no parameter that renames /login.

AuthEndpoints splits the same jobs into modules. Map the ones you need:

ModuleWhat it adds
MapIdentityManagementApiRegister, confirm email, resend, forgot/reset password, manage info, set password, 2FA, ReAuth
MapPasswordEndpointsRegister, forgot/reset password, and login for cookie or Identity bearer. PasswordLoginMode.None omits login.
MapConfirmEmailEndpointsGET /confirmEmail and POST /resendConfirmationEmail, once per endpoint name
MapAccountManagementEndpointsManage info, set password, 2FA, auth methods, and POST /confirmIdentity
MapCookieAuthEndpointsCookie POST /login, POST /logout, GET /csrfToken
MapCookieSessionEndpointsCookie POST /logout and GET /csrfToken
MapBearerAuthEndpointsIdentity bearer POST /login, POST /refresh, POST /logout
MapBearerSessionEndpointsIdentity bearer POST /refresh and POST /logout
MapJwtAuthEndpointsJWT POST /create, POST /refresh, GET /verify, POST /logout, GET /csrfToken
MapJwtPasswordEndpointsJWT POST /create
MapJwtSessionEndpointsJWT refresh, verify, logout, and CSRF token
MapPasskeyEndpointsPasskey register, login, and credential management
MapPasskeySignInEndpointsPasskey register and login, plus confirm-email on the parent group when that name is new
MapPasskeyManagementEndpointsSigned-in passkey list, add, rename, and delete

The 3.x facade (MapAuthEndpoints) is one composition: management plus either cookie or Identity bearer sign-in under IdentityPath (default /identity). Passkeys are on by default under PasskeyPath (default /account). JWT is off until Jwt.Enabled is true.

This maps management and cookie sign-in under /api/account, with passkeys and JWT left off:

builder.Services.AddAuthEndpoints<AppUser, AppDbContext>(o =>
{
    o.IdentityPath = "/api/account";
    o.Passkeys.Enabled = false;
});
builder.Services.AddTransient<IEmailSender<AppUser>, MyEmailSender>();

var app = builder.Build();
app.UseAuthEndpoints();
app.MapAuthEndpoints<AppUser>();

POST /api/account/login on that host is cookie login. The relative names stay /login, /register, and /logout. AuthEndpoints does not rename those segments. MapIdentityManagementApi always includes /register. MapCookieAuthEndpoints always includes /login. The smaller maps omit a piece: MapPasswordEndpoints with PasswordLoginMode.None skips /login, and MapCookieSessionEndpoints maps logout and the CSRF token. An email-code host uses those pieces. See Compose a custom auth stack.

To skip cookie login and map JWT instead, compose the modules. Register the matching services first, call modelBuilder.UseRefreshToken() on the DbContext, and migrate. The route map is:

app.MapGroup("/account").MapIdentityManagementApi<AppUser>();
app.MapGroup("/auth").MapJwtAuthEndpoints<AppUser>();

Full host setup: Compose a custom auth stack. Prefix reference: Configuration options.

JWT, opaque bearer tokens, and cookies

MapIdentityApi login without useCookies or useSessionCookies returns Identity bearer tokens (accessToken, refreshToken, expiresIn, tokenType). Those values are data-protection tickets (BearerTokenProtector and RefreshTokenProtector), not JWTs. Microsoft's Identity API article states that the tokens are not standard JSON Web Tokens. Another service validates them with the Identity bearer handler and the same data-protection keys. JwtBearer and a signing key do not validate them. ?useCookies=true or ?useSessionCookies=true on that same /login issues the application cookie and does not return a token body.

AuthEndpoints has three password sign-in stacks. Identity bearer is the same ticket format as MapIdentityApi. JWT is a separate opt-in.

StackWhat login returnsRefresh
Cookie (facade default)Application cookie. Empty body.The browser sends the cookie.
Identity bearerOpaque accessToken and refreshToken in the bodyPOST /identity/refresh with { "refreshToken" }
JWTJWT accessToken in the body, refresh token in the AuthEndpoints.Jwt.RefreshToken cookiePOST /auth/refresh with that cookie and a CSRF token. The stored token is a hash. Each refresh rotates it. Reuse of an old token revokes the family.

On the 3.x cookie facade, POST /identity/login always uses the application cookie. useCookies is ignored there. A persistent cookie is ?useSessionCookies=false. The flag table for Identity bearer login matches MapIdentityApi. The password facade uses /auth and the same cookie rule. Details: Pick your setup and Stock Identity endpoints vs AuthEndpoints.

Turn JWT on beside the cookie facade. You still call modelBuilder.UseRefreshToken() and add a migration:

builder.Services.AddAuthEndpoints<AppUser, AppDbContext>(o =>
{
    o.Passkeys.ServerDomain = "example.com"; // required in Production while passkeys stay on
    o.Jwt.Enabled = true;
    o.Jwt.Configure = jwt =>
    {
        jwt.Issuer = "https://example.com";
        jwt.Audience = "https://example.com";
        jwt.SigningOptions.SymmetricKey = builder.Configuration["Jwt:SymmetricKey"];
    };
});

MapAuthEndpoints then also maps /auth/create, /auth/refresh, /auth/verify, /auth/logout, and /auth/csrfToken. Signing keys, algorithms, and lifetimes: JWT module.

Passkeys

MapIdentityApi does not map passkey routes. The .NET 10 Blazor Identity template has passkey pages, and its MapAdditionalIdentityEndpoints posts to /Account/PasskeyCreationOptions and /Account/PasskeyRequestOptions. Those routes belong to that template. They are not MapIdentityApi, and they are not a passkey register, login, and credential API for a separate client.

AuthEndpoints maps that API. The 3.x facade turns it on by default under {PasskeyPath}/passkeys (/account/passkeys). In Production set Passkeys.ServerDomain, or set Passkeys.Enabled to false to skip passkey services and routes. The password facade maps passkeys only when Passkeys.ServerDomain is set, under /auth/passkeys. See Pick your setup.

builder.Services.AddAuthEndpoints<AppUser, AppDbContext>(o =>
{
    o.Passkeys.ServerDomain = "example.com";
});

Mapped routes include POST /account/passkeys/register, POST /account/passkeys/login, GET /account/passkeys/, and add, rename, and delete. Passkey sign-in skips two-factor authentication. With no session argument, the completer follows Identity bearer query flags (useCookies or useSessionCookies), not cookie-facade LoginCookie. Pass AuthEndpointsSession.Jwt when passkey sign-in should issue a JWT. Route table and ceremony: Passkeys module. Client steps: Register users and Sign users in.

When MapIdentityApi is enough

Use MapIdentityApi when its ten routes, and a logout handler you write, cover the app. Use AuthEndpoints when you need a logout route, a prefix with only some modules, a JWT access token, or passkey endpoints.