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?
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.
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