Validating user with cookie authentication in ASP.NET Core 2
In a previous post, I wrote about the cookie authentication in ASP.NET Core 2. The cookie authentication does 2 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 read the cookie and set the identity, it doesn't check the user exists. For instance, John logs in on browser A, then, he deletes his account on computer B. When he goes back to the website on browser A, the cookie is still valid, while his account doesn't exist anymore in the database. If you don't care, you may think the user is logged in, whereas it shouldn't.
Of course, the cookie authentication has a hook to validate the user and prevent this kind of situation. Let's see how to use it.
First, you need to handle the OnValidatePrincipal
method in the ConfigureServices
method:
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, you can check the principal corresponds to an actual user. In case the user is invalid, you can prevent the user from being authenticated by calling the RejectPrincipal
method. The validation logic depends on your application. So, the following code is just an example:
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 very easy to use. However, authentication is not as easy as you think. It's very easy to forget something, and get unexpected behaviors.
Do you have a question or a suggestion about this post? Contact me!