In a previous post, I wrote about cookie authentication in ASP.NET Core 2. Cookie authentication does two things:
- Write a cookie with encrypted data when the user logs in
- Read the cookie, decrypt it, and set the request identity (Request.User.Identity)
When it reads the cookie and sets the identity, it does not check whether the user still exists. For example, John logs in on browser A, then deletes his account on browser B. When he returns to the website on browser A, the cookie is still valid even though his account no longer exists in the database. If you ignore this, you may incorrectly treat the user as logged in when they should not be.
Cookie authentication provides a hook to validate the user and prevent this scenario. Here is how to use it.
First, handle the OnValidatePrincipal event in the ConfigureServices method:
C#
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Events.OnValidatePrincipal = PrincipalValidator.ValidateAsync;
});
}
Then, check that the principal corresponds to a real user. If the user is invalid, reject authentication by calling the RejectPrincipal method. The validation logic depends on your application, so the following code is just an example:
C#
public static class PrincipalValidator
{
public static async Task ValidateAsync(CookieValidatePrincipalContext context)
{
if (context == null) throw new System.ArgumentNullException(nameof(context));
var userId = context.Principal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value;
if (userId == null)
{
context.RejectPrincipal();
return;
}
// Get an instance using DI
var dbContext = context.HttpContext.RequestServices.GetRequiredService<IdentityDbContext>();
var user = await dbContext.Users.FindByIdAsync(userId);
if (user == null)
{
context.RejectPrincipal();
return;
}
}
}
Cookie authentication is straightforward to use, but it has subtle pitfalls. It is easy to overlook edge cases and introduce unexpected behavior.
Do you have a question or a suggestion about this post? Contact me!