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.
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);
});
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With