Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my JWT bearer authentication recognizing tokens as expired 5 minutes after the token says?

I'm using AspNet.Security.OpenIdConnect.Server to issue JWT tokens and have the AuthorizationCodeLifetime set to 30 seconds for testing. Here is the snippet of code I'm using to set the options

        options.TokenEndpointPath = "/api/token";
        options.AllowInsecureHttp = true;
        options.AccessTokenHandler = new JwtSecurityTokenHandler();
        options.SigningCredentials.Add(new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256));
        options.AccessTokenLifetime = TimeSpan.FromSeconds(30);
        options.AuthorizationCodeLifetime = TimeSpan.FromSeconds(30);

The returned token contains:

  "expires_in": 30,

and the deserialized token contains the following claims:

 "nbf": 1476915220,
 "exp": 1476915250,
 "iat": 1476915220,

As you can see, the exp (expire time) is 30 seconds after the iat (issue time). Yet, the token isn't triggering an 401 Unauthorized until 5 minutes after it expires. If we plug the exp number in to http://www.epochconverter.com/ then we will see that this evaluates to Wed, 19 Oct 2016 22:14:10 GMT which is 5:14:10 PM in my local time.

Here is some logged output from the Core library :

Microsoft.IdentityModel.Tokens.SecurityTokenExpiredException: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '10/19/2016 22:14:10'
Current time: '10/19/2016 22:19:10'.
   at Microsoft.IdentityModel.Tokens.Validators.ValidateLifetime(Nullable`1 notBefore, Nullable`1 expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateLifetime(Nullable`1 notBefore, Nullable`1 expires, JwtSecurityToken securityToken, TokenValidationParameters validationParameters)
   at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateToken(String token, TokenValidationParameters validationParameters, SecurityToken& validatedToken)
   at Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler.<HandleAuthenticateAsync>d__1.MoveNext()
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerMiddleware[7]
      Bearer was not authenticated. Failure message: IDX10223: Lifetime validation failed. The token is expired.
ValidTo: '10/19/2016 22:14:10'
Current time: '10/19/2016 22:19:10'.

Of course, this output doesn't prove that the server was accepting the token between 22:14:10 and 22:19:10. This is what it would have done had I happen to have waited 5 minutes after it was expired and then tried to validate the token, but you'll have to take my word for it. I was testing in Postman and was clicking "Send" every second until it returned a 401.

So, what's up? Is there some 5 minute buffer built in that I'm not aware of? Interestingly, the default value for the AccessTokenLifetime is 5 minutes, but the token is definitely reflecting that I've changed it to 30 seconds. What's going on?

Relevant libraries I'm using:

<package id="AspNet.Security.OpenIdConnect.Extensions" version="1.0.0-beta6-final" targetFramework="net452" />
<package id="AspNet.Security.OpenIdConnect.Server" version="1.0.0-beta6-final" targetFramework="net452" />
<package id="Microsoft.AspNetCore.Authentication.JwtBearer" version="1.0.0" targetFramework="net452" />
like image 370
Derek Greer Avatar asked Oct 19 '16 22:10

Derek Greer


2 Answers

So, what's up? Is there some 5 minute buffer built in that I'm not aware of? What's going on?

What you call "buffer" is actually a built-in feature offered by the JWT bearer middleware (developed by Microsoft) which is known as "clock skew" and was designed to mitigate the effects of clock desynchronization in web farms.

As you figured out, the default value is set to 5 minutes, but it can be changed via JwtBearerOptions.TokenValidationParameters.ClockSkew.

like image 187
Kévin Chalet Avatar answered Oct 22 '22 23:10

Kévin Chalet


I tried ClockSkew = TimeSpan.Zero:

new TokenValidationParameters
    {
                ClockSkew = TimeSpan.Zero
    };

The reason for this is that ClockSkew has a default value of 5 minutes

like image 1
jhonatan_yachi Avatar answered Oct 22 '22 22:10

jhonatan_yachi