IdentityModel / IdentityModel.AspNetCore.OAuth2Introspection

ASP.NET Core authentication handler for OAuth 2.0 token introspection
Apache License 2.0
147 stars 66 forks source link

Adding custom response body during OnAuthenticationFailed Event creates error "System.InvalidOperationException: StatusCode cannot be set because the response has already started." #155

Closed unionthugface closed 2 years ago

unionthugface commented 2 years ago

I have a business case where I need to add a custom JSON response to 401 unauthorized requests. I am trying to hook into the OnAuthenticationFailed event -- which maybe isn't the right place?

I created a SO question for this: https://stackoverflow.com/questions/71562924/net-core-3-1-oauth-2-token-introspection-with-custom-json-response-status-co

I'll copy/paste some of that here. Here's my code:

services.AddAuthentication("Bearer")
.AddOAuth2Introspection(options =>
{
    options.Authority = _idpAuthority;
    options.ClientId = _apiResourceName;
    options.ClientSecret = _apiResourceSecret;

    options.Events.OnAuthenticationFailed = async context =>
    {
        context.NoResult();
        if (!context.Response.HasStarted)
        {
            context.Response.StatusCode = 401;
            context.Response.ContentType = "application/json";
            await context.Response.Body.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new ErrorListResponseModel().AddError("401", "UNAUTHORIZED")));     
        }

    };
});

This produces the error:

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

The logging callsite of this error is Microsoft.AspNetCore.Server.IIS.Core.IISHttpContext.ReportApplicationError.

Whether I add or remove context.NoResult() -- no difference.

I've tried using a synchronous delegate function and returning Task.CompletedTask instead -- no difference.

If I don't explicitly set the context.Response.StatusCode, I get a 200 status code in the response, AND the error still throws!

The remarks in the library suggest that

Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed.

. . . but I don't see any suggestions on how to suppress these exceptions.

Expected behavior: Write a custom JSON object to 401 unauthorized response without triggering an exception.

unionthugface commented 2 years ago

I'm closing this out because I just stopped using this method and used instead app.UseStatusCodePages:

app.UseStatusCodePages(async (StatusCodeContext statusCodeContext) =>
{
    var context = statusCodeContext.HttpContext;
    if (context.Response.StatusCode == 401)
    {
        await context.Response.Body.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new ErrorListResponseModel().AddError("401", "UNAUTHORIZED")));
    }
});
github-actions[bot] commented 2 years ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue.