tymur999
tymur999

Reputation: 220

AuthorizeAttribute return Custom Value in ASP.net Core (Without Override)

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
}

Note: I have already created a custom authentication policy. I just need it to return a unique value which I don't know how to do.

Upvotes: 2

Views: 1847

Answers (2)

King King
King King

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 AuthorizeFilters 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

Leaky
Leaky

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

Related Questions