Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems handling OnTokenValidated with a delegate assigned in startup.cs

I want to properly use DI in ASP.NET Core 2.0 in order to have my custom method handle the OnTokenValidated event that fires after a JWT token is validated during authentication. The solution below works, except that in the handler I use an injected service that hits MemoryCache to check for cached items added elsewhere in a controller (I've verified that they're added and persisted), and when it's accessed, the cache is always empty. I suspect this is because my custom handler object is being created by a different container (due to the early BuildServiceProvider() call?) and is utilizing a separate instance of MemoryCache (or similar).

If that's the case, I guess I'm not clear on how to properly add and reference my class and method in ConfigureServices() in startup.cs so that it works as intended. Here's what I have:

public void ConfigureServices(IServiceCollection services)
    {
    services.AddMemoryCache();
    ...
    services.AddScoped<IJwtTokenValidatedHandler, JwtTokenValidatedHandler>();
    // add other services
    ...
    var sp = services.BuildServiceProvider();
    services.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, bOptions =>
        {
            // Configure JwtBearerOptions
            bOptions.Events = new JwtBearerEvents
            {
                OnTokenValidated = sp.GetService<JwtTokenValidatedHandler>().JwtTokenValidated
            };
        }

My custom handler class is below. The ValidateSessionAsync() call uses an injected AppSessionService to access the MemoryCache object and ensure a cache entry exists:

public class JwtTokenValidatedHandler : IJwtTokenValidatedHandler
{
    AppSessionService _session;
    public JwtTokenValidatedHandler(AppSessionService session)
    {
        _session = session;
    }
    public async Task JwtTokenValidated(TokenValidatedContext context)
    {
        // Add the access_token as a claim, as we may actually need it
        var accessToken = context.SecurityToken as JwtSecurityToken;
        if (Guid.TryParse(accessToken.Id, out Guid sessionId))
        {
            if (await _session.ValidateSessionAsync(sessionId))
            {
                return;
            }
        }
        throw new SecurityTokenValidationException("Session not valid for provided token.");
    }
}

If the custom OnTokenValidated method contained simple logic and didn't need an injected service I would inline it with an anonymous function or declare it privately in startup.cs. I'd prefer to fix this approach if I can, but I'd be open to other ones.

like image 798
coryseaman Avatar asked Dec 19 '22 03:12

coryseaman


1 Answers

Instead of using static/singleton events, consider subclassing JwtBearerEvents and using the JwtBearerOptions.EventsType option:

public class CustomJwtBearerEvents : JwtBearerEvents
{
    AppSessionService _session;
    public CustomJwtBearerEvents(AppSessionService session)
    {
        _session = session;
    }

    public override async Task TokenValidated(TokenValidatedContext context)
    {
        // Add the access_token as a claim, as we may actually need it
        var accessToken = context.SecurityToken as JwtSecurityToken;
        if (Guid.TryParse(accessToken.Id, out Guid sessionId))
        {
            if (await _session.ValidateSessionAsync(sessionId))
            {
                return;
            }
        }
        throw new SecurityTokenValidationException("Session not valid for provided token.");
    }
}

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<CustomJwtBearerEvents>();

        services.AddAuthentication()
            .AddJwtBearer(options =>
            {
                options.EventsType = typeof(CustomJwtBearerEvents);
            });
    }
}
like image 64
Kévin Chalet Avatar answered Feb 16 '23 00:02

Kévin Chalet