Following the call chain of SignInManager.SignInAsync all the way down to cookie storage reveals how cleanly ASP.NET Core separates concerns across its authentication pipeline. The official ASP.NET Core repo is the reference throughout.
Step 1 — SignInManager
SignInAsync is a thin wrapper. It builds a ClaimsPrincipal from the user, attaches any additional claims, then hands off to HttpContext.SignInAsync:
// SignInManager.SignInAsync delegates to SignInWithClaimsAsync
public virtual Task SignInAsync(TUser user, bool isPersistent, string authenticationMethod = null)
=> SignInWithClaimsAsync(user, isPersistent,
authenticationMethod != null
? new[] { new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod) }
: Array.Empty<Claim>());
// SignInManager.SignInWithClaimsAsync builds the principal and calls HttpContext
public virtual async Task SignInWithClaimsAsync(TUser user, bool isPersistent,
IEnumerable<Claim> additionalClaims)
{
var userPrincipal = await CreateUserPrincipalAsync(user);
foreach (var claim in additionalClaims)
userPrincipal.Identities.First().AddClaim(claim);
await Context.SignInAsync(
IdentityConstants.ApplicationScheme,
userPrincipal,
new AuthenticationProperties { IsPersistent = isPersistent });
}Step 2 — HttpContext Extension
HttpContext.SignInAsync is an extension method in AuthenticationHttpContextExtensions. It resolves IAuthenticationService from the DI container and delegates:
// AuthenticationHttpContextExtensions.cs — the HttpContext.SignInAsync extension
public static Task SignInAsync(this HttpContext context, string scheme,
ClaimsPrincipal principal, AuthenticationProperties properties)
=> context.RequestServices
.GetRequiredService<IAuthenticationService>()
.SignInAsync(context, scheme, principal, properties);Step 3 — IAuthenticationService
The service interface provides the full authentication surface — authenticate, challenge, forbid, sign-in, sign-out:
// IAuthenticationService — the contract resolved from the service container
public interface IAuthenticationService
{
Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
Task SignInAsync(HttpContext context, string scheme,
ClaimsPrincipal principal, AuthenticationProperties properties);
Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
}Step 4 — AuthenticationService
The default implementation (in the Http.Authentication.Core project) resolves the scheme's handler and casts it to IAuthenticationSignInHandler:
// AuthenticationService.SignInAsync — resolves the handler for the scheme
public virtual async Task SignInAsync(HttpContext context, string scheme,
ClaimsPrincipal principal, AuthenticationProperties properties)
{
var handler = await Handlers.GetHandlerAsync(context, scheme) as IAuthenticationSignInHandler;
if (handler == null)
throw new InvalidOperationException(
quot;No IAuthenticationSignInHandler registered for scheme '{scheme}'.");
await handler.SignInAsync(principal, properties);
}
// IAuthenticationSignInHandler — the contract the handler must implement
public interface IAuthenticationSignInHandler : IAuthenticationHandler
{
Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
}Step 5 — Base Handler Dispatch
In the Security project, an abstract base class dispatches the call to the protected HandleSignInAsync method implemented by each concrete handler:
// SignInAuthenticationHandler<TOptions> — base class that dispatches to HandleSignInAsync
public abstract class SignInAuthenticationHandler<TOptions>
: AuthenticationHandler<TOptions>, IAuthenticationSignInHandler
where TOptions : AuthenticationSchemeOptions, new()
{
public virtual Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
=> HandleSignInAsync(user, properties ?? new AuthenticationProperties());
protected abstract Task HandleSignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
}Step 6 — CookieAuthenticationHandler
Added by AddCookie(), this handler is the end of the chain. It wraps the principal in an AuthenticationTicket, encrypts it with ISecureDataFormat, and writes the cookie:
// CookieAuthenticationHandler.HandleSignInAsync — the end of the trail
protected override async Task HandleSignInAsync(
ClaimsPrincipal user, AuthenticationProperties properties)
{
// Wrap the principal in a ticket
var ticket = new AuthenticationTicket(user, properties, Scheme.Name);
// Encrypt / serialize the ticket using the configured data format
var cookieValue = Options.TicketDataFormat.Protect(ticket, GetTlsTokenBinding());
// Write the cookie onto the response
Options.CookieManager.AppendResponseCookie(
Context,
Options.Cookie.Name!,
cookieValue,
BuildCookieOptions(properties, ticket.Properties.IssuedUtc));
}The layered design means you can swap any link in the chain — replace the scheme handler, override the data format, or use a distributed session store — without changing any of the layers above it.
