asp.netasp.net-identitycookie-authentication

ASP.Net Identity SecurityStampValidator is not called if using custom CookieAuthenticationEvents


Using Microsoft.AspNetCore.Identity (.net 7 at time of writing), the default security stamp validation is not done anymore if I use a custom CookieAuthenticationEvents. Configuring the SecurityStampValidatorOptions has no effect either.

Why is this happening and what can be done to enable the security stamp validation?

program.cs

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.ConfigureApplicationCookie(options =>
    options.EventsType = typeof(CustomCookieAuthenticationEvents) 
);//this prevents securityStampValidation from occurring.

//set a small interval between validations so we can debug
builder.Services.Configure<SecurityStampValidatorOptions>(o => o.ValidationInterval = TimeSpan.FromSeconds(10));
builder.Services.Configure<SecurityStampValidatorOptions>(o => o.OnRefreshingPrincipal = c =>
{
    //breakpoint here is hit if ConfigureApplicationCookie(options.EventsType) is NOT set
    return Task.FromResult(0); 
});

builder.Services.AddScoped<CustomCookieAuthenticationEvents>();

CustomCookieAuthenticationEvents.cs is an empty class for now

public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{  }

Solution

  • This behavior should be documented as it falls between two products.

    The doc says

    Events: The Provider may be assigned to an instance of an object created by the application at startup time. The handler calls methods on the provider which give the application control at certain points where processing is occurring. If it is not provided a default instance is supplied which does nothing when the methods are called.

    EventsType: If set, will be used as the service type to get the Events instance instead of the property.

    Fine, but let's not be misleaded by the events definition, as it is part of the namespace Microsoft.AspNetCore.Authentication.Cookies which is not the Identity namespace.

    Instead, let's look at the code for IdentityServiceCollectionExtensions, which, among other things, does for AddIdentity

    .AddCookie(IdentityConstants.ApplicationScheme, o =>
    {
        o.LoginPath = new PathString("/Account/Login");
        o.Events = new CookieAuthenticationEvents
        {
            OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
        };
    })
    

    So the cookie Events is set when adding Identity, and when we add our custom options.EventsType = typeof(CustomCookieAuthenticationEvents), the Events property is dismissed.

    By looking at this code, we see that OnValidatePrincipal is the only event that is set, so we shouldn't have other unexpected missing functionalities. We also see that a static class is used to call the validation, so we can copy that into our CustomCookieAuthenticationEvents.

    public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
    {
        public async override Task ValidatePrincipal(CookieValidatePrincipalContext context)
        {
            await SecurityStampValidator.ValidatePrincipalAsync(context);
            
            //optional custom code
        }
    }