Search code examples
c#oauth-2.0identityserver4asp.net-core-3.1identitymodel

.NET Core 3.1 OAUTH 2 Token Introspection with Custom JSON Response - "Status Code cannot be set because the response has already started"


I have already tried all the solutions here, and nothing is working.

I am using package IdentityModel.AspNetCore.OAuth2Introspection v6.0.0. The business use case I have is that I have to return a 401 Unauthorized status code plus a custom JSON object in the response when authentication fails. This code works to return the JSON object, BUT it throws the same error on the backend each time, and once that starts happening, latency shoots up on my app.

Here's what I have so far:

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.

Please help 😭


Solution

  • I resolved this by taking this code out of the OnAuthenticationFailed event and moving it into the Configure(IApplicationBuilder app) part of Startup.cs:

    app.UseStatusCodePages((StatusCodeContext statusCodeContext) =>
    {
        var context = statusCodeContext.HttpContext;
        if (context.Response.StatusCode == 401)
        {
            context.Response.ContentType = _applicationJsonMediaHeader;
            return context.Response.Body.WriteAsync(_serializedUnauthorizedError).AsTask();
        }
    
        return Task.CompletedTask;
    });