Home > OS >  Authorization is continuing after a jwt token has failed validation
Authorization is continuing after a jwt token has failed validation

Time:10-23

I have implemented an api which supports jwt token authorization with the following code:

services.AddAuthentication(bearerScheme)
    .AddJwtBearer(bearerScheme, config =>
    {
        config.Authority = authority;
        config.Audience = resourceId;

I have also implemented a custom attribute which provides further permission based validation that comes along with its own AuthorizationPolicyProvider and AuthorizationHandler. Sending a valid token works with no issues. The default JwtSecurityTokenHandler fires and validates the token before my customer handler fires and uses the token to validate the user permissions. Sending an expired token however doesn't work as I'd expect. What seems to happen is that JwtSecurityTokenHandler fires and rejects the token with the following error - as expected

IDX10223: Lifetime validation failed. The token is expired.

What I don't understand is why my AuthorizationHandler is then fired which then uses the invalid access token to contact another api and fails because the token is invalid. Ideally, I'd like to either:

  1. Have the jwt validation stop and return a 401 error because the token failed, or
  2. Somehow be able to get the result of the jwt validation in my handler and throw the error myself

Does anybody know how I might do this? I'd quite like to understand why an attribute on a controller method is also being handled when authentication has failed as it seems odd to me to continue after failing authentication?

CodePudding user response:

Short answer

Most likely, the config on .AddJwtBearer() somehow have final setting on (JwtBearerOptions).TokenValidationParameters.ValidateLifetime was set to false.

An other possibility was system clock on server have some problem (which rarely happen).

To make it fail right at Authentication step, try either

.AddJwtBearer(options =>
    {
        //... Some other config
        options.TokenValidationParameters.ValidateLifetime = true;
        //... In case options.TokenValidationParameters.ValidateLifetime still got override somehow, do this
        options.TokenValidationParameters.LifetimeValidator = (nbf, exp, _, _) => nbf < DateTime.UtcNow && exp > DateTime.UtcNow;
    }
)

Under the hood

We got to start at AuthenticationMiddleware. Schemes.GetRequestHandlerSchemesAsync() won't take back anything to iterate (furthur investigate at IAuthenticationRequestHandler vs IAuthenticationHandler if there was some curious).

var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); would return the default authentication scheme, which we all know. Then it call context.AuthenticateAsync(), which is an extension here, make use of AuthenticationService. Now AuthenticationHandler will be served as classic. Which will do nothing than transform a jwt token to ClaimPrincipal object. If it doesn't fail fast at this process step, it would flow down to AuthorizationMiddleware, which would definitely evaluate our policy (aka, AuthorizationHandler code block would fired no matter what).

So, everything we got to do now is to make the transform process from jwt back to ClaimPrincipal object to be fail. That way, AuthorizationHandler would not fire, which can be accomplished up Short answer section.

CodePudding user response:

In ASP.NET Core, successful authentication isn't by default a requirement for authorization, because they serve different purposes:

  • Authentication: restore user's identities
  • Authorization: decide whether to grant access

In the request pipeline (if this is where the authorization is triggered), authorization middleware attempts to restore the user's identities using the given authentication schemes on the authorization policy (if any). If none of the authentication schemes succeeded, the authentication result is simply NO RESULT. In this case, the authorization policies will be evaluated against an unauthenticated user.

If you're enforcing an authenticated user, you will need to explicitly specify it when constructing the policy by using RequireAuthenticatedUser:

new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .Build();

You can also check the User.Identity.IsAuthenticated to determine if the user is authenticated in your authorization handling code and only make a call to your API for authenticated scenarios.

You also have the choice to either ignore unauthenticated users and let other authorization handlers to decide if the "anonymous user" should have access to resource based on other factors, or fail it loudly by returning a failed authorization result from your code.

If you're interested in the source code, have a look at PolicyEvaluator.

  • Related