Reputation: 220
I'm using ASP.NET Core 5.0 and making a REST API. I'm also using JWT tokens for authentication.
[Authorize]
public class LoginController : Controller
I have already added Authorization/Authentication middleware for JWT. How can I get AuthorizeAttribute to return a custom value if the authentication fails? Currently, it does this:
Authorize Good token: nothing
Authorize Bad token: Return status 401/403
But I want it to do something like this where it returns 200 OK but with JSON response in the body, due to a need for consistency with my other responses and to be browser friendly
Authorize Good token: nothing
Authorize Bad token: 200 OK <json>
Here is a template for what I would use for my JSON
{
succeeded: boolean
statusCode: number //http status code
response: array of strings //general info such as errors
}
Upvotes: 2
Views: 1847
Reputation: 63317
Here's another solution by trying to follow the pattern of cross-cutting concerns. With this solution, of course we don't have to repeat the code for each authentication scheme.
First you cannot use IAuthorizationFilter
and IAsyncAuthorizationFilter
for this scenario because all the filters are executed in sequence and that sequence is not ensured to be in a determined order (usually at the step of policy combination, all AuthorizeFilter
s are appended after all other filters). So at the time your filter runs, the result may have not been set, you don't even know if the authorization is succeeded or failed. You also cannot use IResultFilter
and IAsyncResultFilter
because in case of some Result
is set after an authorization filter runs, the result filters will not be run.
Luckily we have one kind of filter that always runs in that case. That is IAlwaysRunResultFilter
and IAsyncAlwaysRunResultFilter
. So you can add one filter of that kind and override the result, something like this:
public class CustomAsyncAlwaysRunResultFilterAttribute : Attribute, IAsyncAlwaysRunResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context.HttpContext.Response.StatusCode == 403 ||
context.Result is ForbidResult)
{
//set the result of your choice
context.Result = new JsonResult(new { ... });
}
await next();
}
}
In the ForbidResult
(if set), you have access to a list of authentication schemes (if empty, the default scheme has been used), which may be helpful.
Note that the ForbidResult
may be set by one of the AuthorizeFilter
or one custom filter of yours. However if your custom filter sets some result of a different type, the code above has no knowledge about that and may not easily know how to handle it. The good practice here is always use ForbidResult
if you write other custom authorization filters which join in authorizing the request.
Finally, if you use authorization middleware (introduced as a separate middleware since asp.net core 3.0
), I doubt that you can implement an IAuthorizationMiddlewareResultHandler
to intercept & override the result there. I however did not test this way (and have never tried it yet). But I include it in here for you to try it yourself (it would be nice if you let me know the result you tried, working or not):
public class CustomAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler {
public Task HandleAsync (RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult){
//here you have access to the AuthorizationPolicy
//but note that the policy contains no name (as added in the configuration)
//it's kind of combined set of authentication schemes & handlers.
}
}
Upvotes: 0
Reputation: 3636
Well, this is not so pretty (in terms of its placement), but should work fine:
.AddJwtBearer(options =>
{
// ...
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = ctx =>
ctx.Response.WriteAsJsonAsync(new
{
suceeded = false,
statusCode = yourStatusCode,
response = yourStringArray
})
};
// ...
}
Probably you're getting the gist of it. You can set handlers for the various JWT token authentication events. WriteAsJsonAsync()
automatically sets 200 status code and applicaton/json content-type. But you can also write a string with WriteAsync()
, and set custom status code and content-type.
Upvotes: 1