Recipes
Recipe matrix
| Recipe | Management group | Sign-in group | Passkeys |
|---|---|---|---|
| Cookie web client (default facade) | /identity | cookie /identity | /account |
| Identity bearer | /identity | bearer /identity | optional |
| JWT-only | e.g. /account | JWT /auth | optional |
| Custom paths | any prefix | matching DI | any prefix |
Which recipe for which client?
| Client | Recommended sign-in | Why |
|---|---|---|
| Browser / SPA (React, Next.js, Vue, Nuxt, Svelte, …) | Cookie (default facade) | Session cookie + CSRF fits first-party web apps; passkeys work well here |
| Browser wanting JWT access tokens | JWT | Short-lived Bearer access token; refresh stays in an HttpOnly cookie + CSRF |
| Native / mobile apps | Identity bearer | Access and refresh tokens in the API response — no cookie jar or CSRF for refresh |
| Mixed web + native | Cookie or JWT for web, Identity bearer for native | Map separate sign-in groups (or hosts) per client type |
Passkeys are WebAuthn-oriented (browsers and platform passkey APIs). With the default Identity completer, register/login issue an Identity bearer token, or an application cookie when ?useCookies=true / ?useSessionCookies=true. For Simple JWT after passkey, register JwtPasskeySignInCompleter (see Passkeys) — do not call password JWT /create after a successful passkey.
Advanced composition
Compose modules yourself when you need bearer Identity, custom paths, or JWT-only. Map management once in production hosts.
builder.Services
.AddIdentityApiEndpoints<AppUser>(o =>
{
o.Stores.SchemaVersion = IdentitySchemaVersions.Version3;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
builder.Services.AddAntiforgery();
builder.Services.AddCookieAuthEndpoints(); // rate limits + ReAuth schemes
builder.Services.AddPasskeyEndpoints<AppUser>();
builder.Services.AddJwtEndpoints<AppUser, AppDbContext>(o =>
{
o.Issuer = "https://example.com";
o.Audience = "https://example.com";
o.SigningOptions.SymmetricKey = builder.Configuration["Jwt:SymmetricKey"];
});
// Optional: Simple JWT after passkey register/login
// builder.Services.AddPasskeySignInCompleter<AppUser, JwtPasskeySignInCompleter<AppUser>>();
// In AppDbContext.OnModelCreating:
// builder.UseRefreshToken();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UseRateLimiter();
app.UseAntiforgery();
// Cookie web client
app.MapGroup("/identity").MapIdentityManagementApi<AppUser>();
app.MapGroup("/identity").MapCookieAuthEndpoints<AppUser>();
// Or Identity bearer
// app.MapGroup("/identity").MapIdentityManagementApi<AppUser>();
// app.MapGroup("/identity").MapBearerAuthEndpoints<AppUser>();
// Or JWT-only (no cookie login)
// app.MapGroup("/account").MapIdentityManagementApi<AppUser>();
// app.MapGroup("/auth").MapJwtAuthEndpoints<AppUser>();
app.MapGroup("/account").MapPasskeyEndpoints<AppUser>();
Cookie web client
Best default for a first-party web app on the same site (or carefully configured CORS + credentials).
- Management + cookie on
/identity - Passkeys on
/account - Client uses credentials + CSRF token
Identity bearer
Best default for native and mobile clients. Use when the client stores Identity bearer tokens (ASP.NET Core Identity API token format) instead of cookies.
- Map
MapBearerAuthEndpointsinstead of cookie - CSRF is skipped for bearer-authenticated unsafe methods (see Requirements)
JWT-only
Best for browser clients that want a Bearer access token with an HttpOnly refresh cookie (not a pure token refresh flow for native apps).
- Management on one prefix (e.g.
/account) MapJwtAuthEndpointson/auth(or your choice)- Still call
UseRefreshToken()and migrate - For passwordless + Simple JWT, register
JwtPasskeySignInCompleter(not password/createafter passkey)
Custom paths
Prefixes are arbitrary as long as DI and middleware match. The Demo host uses paths such as /auth/cookie, /auth/passkey, and /auth/jwt via facade options.