Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return custom status code on authentication failure .Net Core

I have an custom authentication scheme in .Net core where I want to return a specific response for some specific kinds of authentication failures (as opposed to just returning a 401). My current approach looks something like this:

protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var token = GetToken();
        if (string.IsNullOrWhiteSpace(token))
        {
            return AuthenticateResult.NoResult();
        }

        var validationResult = _tokenContextProvider.Validate(); // validate

        if (validationResult != TokenValidationResult.Ok)
        {
            await updateResponseCode(Context, validationResult);
            return AuthenticateResult.Fail("Token invalid");
        }
        
        //success
        var userContext = tokenContextProvider.GetUserContext();
        var ticket = GetAuthenticationTicket(userContext);
        return AuthenticateResult.Success(ticket);
    }


    private static async Task updateResponseCode(HttpContext context, TokenValidationResult validationResult)
    {
        if (!context.Response.HasStarted)
        {
            if (validationResult == TokenValidationResult.SpecialError)
            {
                context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
                var errorMessage = JsonConvert.SerializeObject(new
                {
                    error = new
                   {
                      message = validationResult.Message
                    }
                 });
                context.Response.Headers["Content-Type"] = "application/json";
                await context.Response.WriteAsync(errorMessage, Encoding.UTF8);                
          }
        }
    }

This is fine and returns the response code I want. However, another bit of middleware (I'm assuming) is then trying to set the StatusCode after and resulting in some exceptions being logged.

Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext in ThrowResponseAlreadyStartedException

System.InvalidOperationException: StatusCode cannot be set because the response has already started.

I can just "ignore" the exception but I would rather find some way to do this that is supported a bit more cleanly so I don't clutter the logs with these exceptions.

Is there some other way to change the response returned after a failed authentication?

like image 883
Rondel Avatar asked Apr 29 '26 02:04

Rondel


1 Answers

In case someone runs into a similar problem, I essentially took a different approach. Instead of attempting to force the authentication handler to do something it wasn't built to do, I pulled the StatusCode setting logic into its own middleware.

During the authentication process, when the event I care about happens, I add an item to the HttpContext like this

 protected override Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        var token = GetToken();
        ...

        if (validationResult != TokenValidationResult.Ok)
        {
            Context.Items.Add("TokenValidationResult", validationResult);
            return Task.FromResult(AuthenticateResult.Fail("Token invalid"));
        } 

The middleware then has a chance to run after the other handlers run

public async Task Invoke(HttpContext httpContext)
    {
        await _next(httpContext);
        // context Item ready here
        var tokenValidationResult = getTokenValidationResult(httpContext);
        if (tokenValidationResult != null && tokenValidationResult != TokenValidationResult.Ok)
        {
            await updateResponseCode(httpContext, tokenValidationResult);
        }
    }

    private static async Task updateResponseCode(HttpContext context, TokenValidationResult validationResult)
    {
        if (!context.Response.HasStarted)
        {
            if (validationResult == TokenValidationResult.SpecialError)
            {
                await setErrorMessageAndCode(context, HttpStatusCode.Gone, validationResult);
            }
            else
            {
                await setErrorMessageAndCode(context, HttpStatusCode.Forbidden, validationResult);
            }
        }
    }

The check for !context.Response.HasStarted is still required, but now I can update the status code dynamically without having downstream impact. I registered the middleware to run before the authentication one (so it runs after on the way back out)

app.UseCustomJwtStatusCodes();
app.UseAuthentication();
app.UseAuthorization();

That did the trick for me and allows me to set the error code without interfering with any other middleware.

like image 51
Rondel Avatar answered May 01 '26 16:05

Rondel



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!