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 */) {
httpContext.Items["myObject"] = new MyClass(); // I want to pass that to the endpoint
context.Succeed(requirement);
return;
}
// If it fails at this point, I want to return 404 because of reasons.
httpContext.Response.StatusCode = 404;
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsync("Blah blah NotFound").ConfigureAwait(false);
}
context.Fail();
}
}
I've seen similar code snippets in other StackOverlflow answers. (e.g. here :
CodePudding user response:
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;