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 actually 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 is depend of 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;
        }
    }
}

The 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.

Comments

trailmax -

Does ASP.NET Core v2 not have the same class as SecurityStampValidator as in old-style MVC? I mean do I have to write it myself or there is something already provided by the framework?

Meziantou -

Hi,

Yes, ASP.NET identity provides this functionality. You can use it very easily in ASP.NET Core 2.0:

services.AddAuthentication()
    .AddCookie(options =>
    {
        // Require Microsoft.AspNetCore.Identity
        options.Events.OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync;
    });
trailmax -

Thanks! Found just the same in their source code

Leave a reply