Custom 401 and 403 response model with UseJwtBearerAuthentication middleware

杀马特。学长 韩版系。学妹 提交于 2019-11-30 02:43:09

问题


I want to respond with a JSON response model when a 401 and 403 occur. For example:

HTTP 401
{
  "message": "Authentication failed. The request must include a valid and non-expired bearer token in the Authorization header."
}

I am using middleware (as suggested in this answer) to intercept 404s and it works great, but it is not the case with 401 or 403s. Here is the middleware:

app.Use(async (context, next) =>
{
    await next();
    if (context.Response.StatusCode == 401)
    {
        context.Response.ContentType = "application/json";
        await context.Response.WriteAsync(JsonConvert.SerializeObject(UnauthorizedModel.Create(), SerializerSettings), Encoding.UTF8);
    }
});

When placed BELOW app.UseJwtBearerAuthentication(..) in Startup.Configure(..), it seems to be completely ignored and a normal 401 is returned.

When placed ABOVE app.UseJwtBearerAuthentication(..) in Startup.Configure(..), then the following exception is thrown:

Connection id "0HKT7SUBPLHEM": An unhandled exception was thrown by the application. System.InvalidOperationException: Headers are read-only, response has already started. at Microsoft.AspNetCore.Server.Kestrel.Internal.Http.FrameHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value) at Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_ContentType(String value) at MyProject.Api.Startup.<b__12_0>d.MoveNext() in Startup.cs


回答1:


Set was on the right track, but there's actually no need to create your own middleware, as you can leverage the events model to override the default challenge logic.

Here's an example that will return a 401 response containing the OAuth2 error code/description as plain text (you can of course return JSON or whatever you want):

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
    Authority = "http://localhost:54540/",
    Audience = "http://localhost:54540/",
    RequireHttpsMetadata = false,
    Events = new JwtBearerEvents
    {
        OnChallenge = async context =>
        {
            // Override the response status code.
            context.Response.StatusCode = 401;

            // Emit the WWW-Authenticate header.
            context.Response.Headers.Append(
                HeaderNames.WWWAuthenticate,
                context.Options.Challenge);

            if (!string.IsNullOrEmpty(context.Error))
            {
                await context.Response.WriteAsync(context.Error);
            }

            if (!string.IsNullOrEmpty(context.ErrorDescription))
            {
                await context.Response.WriteAsync(context.ErrorDescription);
            }

            context.HandleResponse();
        }
    }
});

Alternatively, you can also use the status code pages middleware, but for 403 responses, you won't have any hint about the authorization policy that caused it:

app.UseStatusCodePages(async context =>
{
    if (context.HttpContext.Request.Path.StartsWithSegments("/api") &&
       (context.HttpContext.Response.StatusCode == 401 ||
        context.HttpContext.Response.StatusCode == 403))
    {
        await context.HttpContext.Response.WriteAsync("Unauthorized request");
    }
});



回答2:


First of all, order of middlewares is important.

Each middleware chooses whether to pass the request on to the next component in the pipeline, and can perform certain actions before and after the next component is invoked in the pipeline

UseJwtBearerAuthentication stops further pipeline execution if error occurred.

But your approach does not work with JwtBearerAuthentication middleware, as when you have unauthorized error, middleware sends WWWAuthenticate header, that why you get "response has already started" exception - look into HandleUnauthorizedAsync method. You can override this method and implement your own custom logic.

Another possible solution (not sure that works) is to use HttpContext.Response.OnStarting callback in your middleware, as it is called before header send. You cal look on this SO answer



来源:https://stackoverflow.com/questions/38281116/custom-401-and-403-response-model-with-usejwtbearerauthentication-middleware

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!