Search code examples
asp.net-coregoogle-chrome-devtools.net-6.0asp.net-authorization

Asp.Net AuthorizationHandler's response upsets Chrome, causes "net::ERR_HTTP2_PROTOCOL_ERROR"


I've decided to write a custom AuthorizationHandler for a custom Policy I'm using :

// I pass this to AddPolicy in startup.cs
public class MyRequirement : IAuthorizationRequirement {

    public MyRequirement () { ... }
}



public class MyAuthorizationHandler : AuthorizationHandler<MyRequirement> {


    public MyAuthorizationHandler() { }


    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement) {

        if (context.Resource is HttpContext httpContext) {
            var endpoint = httpContext.GetEndpoint();

            if ( /* conditions for hard failure */ ) { context.Fail(); return; }
            
            if ( /* conditions for success */) { context.Succeed(requirement); return; }

            // Neither a success nor a failure, simply a different response.
            httpContext.Response.StatusCode = 404;
            httpContext.Response.ContentType = "application/json";
            await httpContext.Response.WriteAsync("Blah blah NotFound").ConfigureAwait(false);
            return;
        }

        context.Fail();
    }
}

I've seen similar code snippets in other StackOverlflow answers. (e.g. here : How to change status code & add message from failed AuthorizationHandler policy )

Problem : this doesn't seem to generate a "valid" 404 response. I think so for two reasons:

  • When I look at Chrome's network tab, the response is NOT "404", instead it's net::ERR_HTTP2_PROTOCOL_ERROR 404
  • When I look at the response data, there's only headers. My custom error text ("Blah blah NotFound") does not appear anywhere.

What am I doing wrong?

Note : I've tried returning immediately after setting the 404, without doing context.Fail() but I get the same result.


Solution

  • The root cause:

    My Web Api had several middlewares working with the response value. Those middleware were chained up in Startup.cs, using the traditional app.UseXXX().

    Chrome was receiving 404 (along with my custom response body) from my Requirement middleware (hurray!), but Chrome is "smart" and by design continues to receive the response even after that initial 404 -- for as long as the server continues generating some response data.

    Because of that, Chrome eventually came across a different response added by another of the chained up middlewares. The 404 was still there, but the response body was slightly changed.

    And since chrome is paranoid, it would display this net::ERR_HTTP2_PROTOCOL_ERROR to indicate that someone had messed up the consistency of the response somewhere along the chain of responders.

    ==========

    The solution :

    Finalize your response with Response.CompleteAsync() to prevent any other subsequent middleware from changing it further :

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, MyRequirement requirement) {
    
        if (context.Resource is HttpContext httpContext) {
            var endpoint = httpContext.GetEndpoint();
    
            if ( /* conditions for hard failure */ ) { context.Fail(); return; }
            
            if ( /* conditions for success */) {
                context.Succeed(requirement);
                return; 
            }
    
            // Neither a requirement success nor a requirement failure, just a different response : 
    
            httpContext.Response.StatusCode = 404;
            httpContext.Response.ContentType = "application/json";
            await httpContext.Response.WriteAsync("Blah blah NotFound");
            await httpContext.Response.CompleteAsync(); // <-- THIS!!!
            return;
        }
    
        context.Fail();
    }
    

    Please note : if your 'HandleRequirementAsync' function does not have the 'async' keyword, then do not use 'await' inside of it, and do return Task.CompletedTask; instead of just return;