50

I am currently using the JwtSecurityToken class in System.IdentityModels.Tokens namespace. I create a token using the following:

DateTime expires = DateTime.UtcNow.AddSeconds(10);
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var genericIdentity = new System.Security.Principal.GenericIdentity(username, "TokenAuth");

ClaimsIdentity identity = new ClaimsIdentity(claims);
string secret = ConfigurationManager.AppSettings["jwtSecret"].ToString();
var securityKey = new     InMemorySymmetricSecurityKey(Encoding.Default.GetBytes(secret));
var signingCreds = new SigningCredentials(securityKey,     SecurityAlgorithms.HmacSha256Signature, SecurityAlgorithms.HmacSha256Signature);
var securityToken = handler.CreateToken(
    issuer: issuer,
    audience: ConfigurationManager.AppSettings["UiUrl"].ToString(),
    signingCredentials: signingCreds,
    subject: identity,
    expires: expires,
    notBefore: DateTime.UtcNow
);
return handler.WriteToken(securityToken); 

For some reason even though the expires is set to 10 seconds after the current time it doesn't actually throw an exception when the token is being validated until about 5 minutes. After seeing this, I thought maybe there was a minimum expire time of 5 minutes, so I set the expire time to:

DateTime.UtcNow.AddMinutes(5);

Then it expires at 10 minutes, but the exception message says that the expire time is set to what it is supposed to be (5 minutes after the user logs in), and when it shows the current time in the exception it is 5 minutes after the expire time. So, it seems to know when it SHOULD expire, but it doesn't actually throw the exception until 5 minutes after the expire time. Then, since the token seems to be adding 5 minutes to whatever time I set it to expire I set the expire time to:

DateTime.UtcNow.AddMinutes(-5).AddSecond(10);

I tested this and so far it still hasn't expired (After more than ten minutes). Can someone please explain why this is happening and what I am doing wrong? Also, if you see anything else with the code I provided any guidance would be appreciated since I am new to using JWTs and this library.

Luke Girvin
  • 13,221
  • 9
  • 64
  • 84
tkd_aj
  • 993
  • 1
  • 9
  • 16

7 Answers7

112

The problem is related ClockSkew. Normally, the validation libraries (at least the MS one) compensate for clock skew. ClockSkew default value is 5 minutes. See some answer here

You can change ClockSkew in TokenValidationParameters:

var tokenValidationParameters = new TokenValidationParameters
{
    //...your setting

    // set ClockSkew is zero
    ClockSkew = TimeSpan.Zero
};

app.UseJwtBearerAuthentication(new JwtBearerOptions
{
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    TokenValidationParameters = tokenValidationParameters
});
starball
  • 20,030
  • 7
  • 43
  • 238
hien
  • 1,988
  • 2
  • 17
  • 13
15

After reading through @Denis Kucherov's answer, I found out that I could use the same custom validator he posted without using the JwtBearerOptions class which would have required me to add a new library.

Also, Since there are two namespaces which contain a lot of these same classes I will make sure to mention that all of these are using the System.IdentityModels... namespaces. (NOT Microsoft.IdentityModels...)

Below is the code I ended up using:

private bool CustomLifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken tokenToValidate, TokenValidationParameters @param)
{
    if (expires != null)
    {
        return expires > DateTime.UtcNow;
    }
    return false;
}
private JwtSecurityToken ValidateJwtToken(string tokenString)
{
   string secret = ConfigurationManager.AppSettings["jwtSecret"].ToString();
   var securityKey = new InMemorySymmetricSecurityKey(Encoding.Default.GetBytes(secret));
   JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
   TokenValidationParameters validation = new TokenValidationParameters()
   {
       ValidAudience = "MyAudience",
       ValidIssuer = "MyIssuer",
       ValidateIssuer = true,
       ValidateLifetime = true,
       LifetimeValidator = CustomLifetimeValidator,
       RequireExpirationTime = true,
       IssuerSigningKey = securityKey,
       ValidateIssuerSigningKey = true,
   };
   SecurityToken token;
   ClaimsPrincipal principal = handler.ValidateToken(tokenString, validation, out token);
   return (JwtSecurityToken)token;
}
tkd_aj
  • 993
  • 1
  • 9
  • 16
13

There are seems to be some issue with LifeTimeValidator. You can just override its logic with a custom delegate. Also, use JwtBearerOptions class to control authentication middleware behavior. For example:

new JwtBearerOptions
{
     AutomaticAuthenticate = true,
     AutomaticChallenge = true,
     TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
     {
           ValidIssuer = _configuration["Tokens:Issuer"],
           ValidAudience = _configuration["Tokens:Audience"],
           ValidateIssuer = true,
           ValidateAudience = true,
           ValidateLifetime = true,
           LifetimeValidator = LifetimeValidator,
           ValidateIssuerSigningKey = true,
           IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Tokens:Key"]))
      }
}

And assign LifetimeValidotor delegate, to provide its own timeout validation logic:

private bool LifetimeValidator(DateTime? notBefore, DateTime? expires, SecurityToken token, TokenValidationParameters @params)
{
     if (expires != null)
     {
          return expires > DateTime.UtcNow;
     }
     return false;
}
John
  • 321
  • 2
  • 12
Denis Kucherov
  • 369
  • 3
  • 15
  • the JwtBearerOptions seems to require another library. I checked it out here: https://www.nuget.org/packages/Microsoft.AspNet.Authentication.JwtBearer and attempted to install the nuget. It fails each time, even though my project is on the .net 4.5.1. "Could not install package 'Microsoft.AspNet.Authentication.JwtBearer 1.0.0-beta8'. You are trying to install this package into a project that targets '.NETFramework,Version=v4.5.1', but the package does not contain any assembly references or content files that are compatible with that framework." Any idea why it won't let me install? – tkd_aj Aug 18 '17 at 20:40
  • @tkd_aj - Try this one: https://www.nuget.org/packages/Microsoft.AspNetCore.Authentication.JwtBearer/ – Denis Kucherov Aug 19 '17 at 06:28
  • That one is for ASP.NET Core. Our project uses ASP.NET 4.5.1. I'm guessing that one isn't going to work. – tkd_aj Aug 21 '17 at 20:04
  • @tkd_aj Try version 1.1.2 it should work on 4.5.1 and .NET Standart 1.4 – Denis Kucherov Aug 22 '17 at 08:46
  • That version of the library installed successfully. After installing it, though, I could not find a way to actually USE the JwtBearerOptions object I had created. I looked all over and only found it used in ASP.NET core as middleware by using the app.use() method. BUT, while exploring I found I could assign the same custom validator you posted above in my existing code where I was validating the token. I will upvote your answer and post the code I ended up using in the end. Thank you for helping me solve this problem! – tkd_aj Aug 25 '17 at 17:22
5

.NET Core Update

This is handled slightly differently in .NET Core, as the TokenValidationParameters are set in Startup.cs using the ConfigureServices() method and then handled automatically by the middleware.

Also note that the older InMemorySymmetricSecurityKey for signing the secret is now deprecated in favor of SymmetricSecurityKey, which is shown below.

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidateAudience = true,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                ValidIssuer = _config.AuthenticationSettings.TokenAuthority,
                ValidAudience = _config.AuthenticationSettings.TokenAuthority,
                LifetimeValidator = TokenLifetimeValidator.Validate,
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes(_config.AuthenticationSettings.SecurityKey))
            };
        });

    // ...
}

And so I also made my own version of the token validator in @tkd_aj's answer above and threw it in a static class:

public static class TokenLifetimeValidator
{
    public static bool Validate(
        DateTime? notBefore,
        DateTime? expires,
        SecurityToken tokenToValidate,
        TokenValidationParameters @param
    ) {
        return (expires != null && expires > DateTime.UtcNow);
    }
}
Kurtis Jungersen
  • 2,204
  • 1
  • 26
  • 31
  • This almost works. However, calling `tokenHandler.ValidateToken(...)` will now throw an `SecurityTokenInvalidLifetimeException` when the `Validate` method returns `false`. – Jens Mar 18 '21 at 22:16
0

I just implemented a JWT token middleware too and although the examples on the internet use UtcNow, I had to use Now or the expire time is off. When I use Now, expiration is spot on.

SledgeHammer
  • 7,338
  • 6
  • 41
  • 86
  • Are you using the 5.0 version of the Jwt library? Or are you using Asp.Net core? I started out using DateTime.Now and I had the same issue. Unfortunately, I am using the 4.0 library because we're stuck using .NET 4.5. The 5.0 version of the Jwt library requires .NET 4.6 so we can't use it. – tkd_aj Sep 27 '16 at 23:27
  • @tkd_aj - ah... I'm using Core. I did have the time off problem with using UtcNow, YMMV :). – SledgeHammer Sep 27 '16 at 23:38
  • Ah, yeah. I saw that AspNet core actually had a nuget package that you could just download and it would do the middleware for you... Unfortunately that isn't available for .NET 4.5. Thank you for your comment, though! – tkd_aj Sep 28 '16 at 01:50
-1

Below link give you the exact answer, as by default MS have expire time of 5mins. So either you have to make it custom or time which you will give in expires: DateTime.Now.AddSeconds(30) 30seconds in above line will be added in expirey time. So total expire time will be 5mins and 30secs

https://github.com/IdentityServer/IdentityServer3/issues/1251

Hope this will help.

Mudit
  • 1
  • The solution in the target link should be copied and adapted here as a proposed answer. Then the source information can be cited as reference. – kall2sollies Dec 17 '21 at 09:16
-1
enter code `  Expires = DateTime.Now.AddHours(4),                

NotBefore= DateTime.Now.AddHours(1), IssuedAt= DateTime.Now.AddHours(1)`

Must the Expires more than NotBefore

Ali Esam
  • 1
  • 2