0

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.

1

1 Answer 1

0

I met the same issue as yours, After searching a lot about this, I find it also need to add OnChallenge property

x.Events = new JwtBearerEvents()
    {
        OnAuthenticationFailed = async 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;
            }

            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
            {
                if (!context.Response.HasStarted)
                {
                    ProblemDetails problem = new()
                    {
                        Title = "token expired",
                        Status = (int)HttpStatusCode.Unauthorized,
                        Type = "token expired",
                        Detail = "token expired"
                    };

                    context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;

                    var result = JsonConvert.SerializeObject(problem);
                    context.Response.ContentType = "application/json";
                    await context.Response.WriteAsync(result);
                }
            }
            return;
        },

        //add this property here.
        OnChallenge = c =>
        {
            c.HandleResponse();
            return Task.CompletedTask;
        }
    };

enter image description here

refer from link.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.