Changelog
Reference

Sign-in completer

The session a successful sign-in finishes in, and how to replace that step.

A sign-in method checks the proof. The completer writes the session after that check succeeds. Failures never call it.

AuthEndpointsSession selects a built-in completer:

SessionNameRegistered byResult
CookieSignInCompleters.CookieAddAuthEndpointsIdentity, AddPasswordEndpoints, or AddEmailCodeEndpointsApplication cookie. Passkey register returns { credentialId }.
IdentityBearerSignInCompleters.IdentityBearersameIdentity bearer body, or an application cookie when useCookies or useSessionCookies is set. Passkey register returns the token body and does not add credentialId.
JwtSignInCompleters.JwtAddJwtEndpointsaccessToken and tokenType. Refresh cookie AuthEndpoints.Jwt.RefreshToken, or refreshToken in the body when transport is Body. Passkey register does not add credentialId.
identity.MapPasswordEndpoints<AppUser>(AuthEndpointsSession.Jwt);
identity.MapEmailCodeEndpoints<AppUser>(AuthEndpointsSession.IdentityBearer);
account.MapPasskeySignInEndpoints<AppUser>(AuthEndpointsSession.Jwt);
external.MapExternalAuthEndpoints<AppUser>(AuthEndpointsSession.Cookie);

MapPasskeySignInEndpoints<TUser>(string?) is still the confirm-email endpoint name. MapPasskeySignInEndpoints<AppUser>("MySession") does not select a completer. When that string matches a registered completer name, map time logs a warning on the AuthEndpoints category and still uses the default completer. Pass the session enum, or pass the completer name and the confirm-email name: MapPasskeySignInEndpoints<AppUser>(SignInCompleters.Jwt, confirmEmailEndpointName: null).

PasskeySignInKind and PasswordLoginMode stay. With no session and no completer argument, email-code verify and passkey register and login keep the Identity bearer completer. Parameterless password login uses the cookie completer. An explicit PasswordLoginMode stays on PasswordSignInAsync. MapCookieAuthEndpoints, MapBearerAuthEndpoints, MapIdentityManagementApi, MapPasskeyEndpoints, and MapAuthEndpoints keep that 3.x choice. AddAuthEndpoints ignores SignInCompletionOptions.Default, including email-code verify and JWT POST /create.

SignInCompletionOptions.Default applies when MapPasswordEndpoints(), MapPasswordSignInEndpoints(), MapEmailCodeEndpoints, or MapPasskeySignInEndpoints is called with no session and no completer name. When Default is unset, those password maps use the Cookie completer. An explicit PasswordLoginMode beats Default and keeps that 3.x handler (Cookie, IdentityBearer, or None).

Custom completer

builder.Services.AddSignInCompleter<AppUser, MySignInCompleter>("MySession");
builder.Services.Configure<SignInCompletionOptions>(options => options.Default = "MySession");

Or pass "MySession" to the sign-in map. A name passed to the map wins over an explicitly registered 3.x completer. The 3.x registration wins over SignInCompletionOptions.Default when the map passes no name. Calling AddSignInCompleter again for the same user and name replaces that registration. The name is checked for the user type being mapped. A completer registered for another user fails at map time. The built-in Cookie, IdentityBearer, and Jwt names apply to every user type.

SignInCompletionContext.UsedTwoFactor is true when password sign-in presented a valid authenticator or recovery code in this request. Cookie and Identity bearer completers add an amr claim of mfa when it is set, and pwd otherwise. A remembered two-factor client stays pwd.

External callbacks use SignInMethod.External. There is no two-factor step. MapExternalAuthEndpoints<TUser>() and MapExternalAuthProvider with no session use the Cookie completer unless SignInCompletionOptions.Default names one. An explicit AddCompleter<T>() still wins over that default. A session or completer argument wins over AddCompleter. AuthEndpointsSession.Jwt writes the refresh cookie and redirects. The access token is not in the URL. That session with RefreshTokenTransport.Body throws at map time and does not create a refresh token. AddCompleter<JwtExternalLoginCompleter<TUser>>() with body transport fails startup validation.

The 3.x completer interfaces are obsolete and removed in 5.0. An explicitly registered one still runs when the map passes no session and no completer. See Upgrade to 4.0.

Password login

MapPasswordEndpoints<TUser>() and MapPasswordSignInEndpoints<TUser>() call CheckPasswordSignInAsync, then the user manager's authenticator and recovery-code checks, then the completer. They do not call PasswordSignInAsync, TwoFactorAuthenticatorSignInAsync, or TwoFactorRecoveryCodeSignInAsync. With no SignInCompletionOptions.Default, the completer is Cookie.

Success, a wrong password, a missing authenticator code, a wrong authenticator code, and remember-client match 3.4.1 cookie login. A wrong recovery code counts toward lockout. TwoFactorRecoveryCodeSignInAsync does not. An unknown user name runs a password-hash verify with the host's IPasswordHasher<TUser>, so that request takes about as long as a wrong password.

A custom SignInManager<TUser> that overrides PasswordSignInAsync, TwoFactorAuthenticatorSignInAsync, or TwoFactorRecoveryCodeSignInAsync is not called by these maps. Override CheckPasswordSignInAsync or CanSignInAsync, or put sign-in side effects in a completer.

An explicit PasswordLoginMode uses the 3.4.1 handlers and beats SignInCompletionOptions.Default. MapCookieAuthEndpoints, MapBearerAuthEndpoints, and MapAuthEndpoints do too. MapAuthEndpoints login does not run the unknown-email hash verify, so an unknown email can return faster than a wrong password.