3

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?

3
  • This is not a responsibility of the auth scheme. The usual approach is to provide events that could be hooked onto in during auth config. See CookieAuthenticationHandler: source.dot.net/#Microsoft.AspNetCore.Authentication.Cookies/… Commented Jul 19, 2021 at 19:58
  • Did you find a solution for this? Commented Jul 27, 2021 at 11:20
  • @MightyAtom I did find a solution. I'll post what I ended up doing Commented Jul 27, 2021 at 13:56

1 Answer 1

4

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.

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.