I use the event OnAuthenticationFailed in JWTBearer only to handle the response when the token expires.
Here's the code:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.SaveToken = true;
x.RequireHttpsMetadata = false;
x.TokenValidationParameters = GetTokenValidationParameters(configuration);
x.Events = GetJWTBearerEvents();
});
internal static JwtBearerEvents GetJWTBearerEvents()
{
return new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// context is AuthenticationFailedContext object
var endpoint = context.HttpContext.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
{
// if endpoint has AllowAnonymous doesn't validate the token expiration
return Task.CompletedTask;
}
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Response.ContentType = "application/json";
var message = $"{ErrorCodes.TokenExpired}: JWT expired.";
var result = new HttpResponse<bool>(
statusCode:(int) HttpStatusCode.Unauthorized,
message: message,
response:false
);
context.Response.WriteAsync(result.ToString());
//Stop propagation to the next middleware here
}
return Task.CompletedTask;
}
};
}
app.UseMiddleware<ExceptionMiddleware>();
app.UseJWTConfig(); // Which is the upper code
app.UseAuthorization();
The problem is that when the token expires goes to the next middleware which is Authorization, even though the response has already started, and then, Authorization middleware tries to set StatusCode to the response throwing an error
StatusCode cannot be set because the response has already started
I need to find a way to stop the middleware propagation at that point.
I tried with context.Fail(context.Exception), context.Response.Abort(), context.Success(), context.NoResult()... and none of those seems to work.
The only way that worked for me was putting a custom middleware in the middle of authentication and authorization.
app.UseJWTConfig();
app.UseMiddleware<IgnoreAuthorizationMiddleware>();
app.UseAuthorization();
public class IgnoreAuthorizationMiddleware
{
private readonly RequestDelegate _next;
public IgnoreAuthorizationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
if (httpContext.Response.HasStarted)
return; //To handle the has started response
await _next(httpContext);
}
}
But I was left with the question if there's a way to stop the middleware propagation at that point. And if you could provide me a better understanding of how works on inside the authorization middleware and why it tries again to send the response, I would appreciate it a lot.
