aspnetcore/breaking-changes/10/cookie-authentication-api-endpoints.md
By default, unauthenticated and unauthorized requests made to known API endpoints protected by cookie authentication now result in 401 and 403 responses rather than redirecting to a login or access-denied URI.
Known API endpoints are identified using the new IApiEndpointMetadata <!--xref:Microsoft.AspNetCore.Http.Metadata.IApiEndpointMetadata--> interface, and metadata implementing the new interface has been added automatically to the following:
[ApiController] endpoints..NET 10 Preview 7
Previously, the cookie authentication handler redirected unauthenticated and unauthorized requests to a login or access-denied URI by default for all requests other than XMLHttpRequests (XHRs).
Starting in .NET 10, unauthenticated and unauthorized requests made to known API endpoints result in 401 and 403 responses rather than redirecting to a login or access-denied URI. XHRs continue to result in 401 and 403 responses regardless of the target endpoint.
This change is a behavioral change.
This change was highly requested. Redirecting unauthenticated requests to a login page doesn't usually make sense for API endpoints, which typically rely on 401 and 403 status codes rather than HTML redirects to communicate auth failures.
If you want to always redirect to the login and access-denied URIs for unauthenticated or unauthorized requests regardless of the target endpoint or whether the source of the request is an XHR, you can override xref:Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents.RedirectToLogin* and xref:Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents.RedirectToAccessDenied* as follows:
builder.Services.AddAuthentication()
.AddCookie(options =>
{
options.Events.OnRedirectToLogin = context =>
{
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
};
});
If you want to revert to the exact previous behavior that avoids redirecting for only XHRs, you can override the events with this slightly more complicated logic:
builder.Services.AddAuthentication()
.AddCookie(options =>
{
bool IsXhr(HttpRequest request)
{
return string.Equals(request.Query[HeaderNames.XRequestedWith], "XMLHttpRequest", StringComparison.Ordinal) ||
string.Equals(request.Headers.XRequestedWith, "XMLHttpRequest", StringComparison.Ordinal);
}
options.Events.OnRedirectToLogin = context =>
{
if (IsXhr(context.Request))
{
context.Response.Headers.Location = context.RedirectUri;
context.Response.StatusCode = 401;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = context =>
{
if (IsXhr(context.Request))
{
context.Response.Headers.Location = context.RedirectUri;
context.Response.StatusCode = 403;
}
else
{
context.Response.Redirect(context.RedirectUri);
}
return Task.CompletedTask;
};
});
Microsoft.AspNetCore.Http.Metadata.IApiEndpointMetadata <!--xref:Microsoft.AspNetCore.Http.Metadata.IApiEndpointMetadata?displayProperty=fullName-->