10

Currently I am programming a ASP.NET-Core WebApi using JWT-Bearer-Authentication.

To make the API accessible from different timezones I am using the following Pattern to set the fields nbf (notBefore) and exp (expires) inside my JWT to a UTC-Timestamp:

var utcNow = DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Unspecified);

...

var tokenOptions = new JwtSecurityToken(
                notBefore: utcNow,
                expires: utcNow.AddSeconds(3600),
            );
...

For token generation, everything works pretty good, nbf and exp contain a UNIX-Timestamp representing the current UTC-Time.

But when doing token validation, everything works for 5 Minutes (my clock-skew setting) and then I only get 401 from API, because the token-validation is done with my current timezone here in Germany.

Is there a way to setup the JwtAuthentication-Middleware in .NET-Core to use UTC-Time for token-validation? Or are there any other ways to solve this?

jps
  • 20,041
  • 15
  • 75
  • 79
DevElch
  • 111
  • 1
  • 7

3 Answers3

8

For a more complete answer, in your Startup.cs:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                // ...
                ValidateLifetime = true,
                LifetimeValidator = (DateTime? notBefore, DateTime? expires, SecurityToken securityToken, 
                                     TokenValidationParameters validationParameters) => 
                {
                    return notBefore <= DateTime.UtcNow &&
                           expires > DateTime.UtcNow;
                }
            };
        });
Serj Sagan
  • 28,927
  • 17
  • 154
  • 183
  • 1
    This doesn't take into account ClockSkew setting. – disklosr Sep 30 '20 at 10:18
  • Why should it? Clock skew will mean a matter of maximum a few seconds difference, which is irrelevant for this type of application... clock skew is an issue for digital circuits to worry about, not software authentication. – Serj Sagan Sep 30 '20 at 17:20
  • 1
    Clock drifts do happen in servers and it can cause a token to randomly be rejected/accepted depending on the server you hit. The reason that that property exist means it matters. – disklosr Oct 01 '20 at 22:04
  • Also, ms's library already validate time in utc so the code kn the answer doesn't provide any added value. – disklosr Oct 01 '20 at 22:05
  • I mean yes, you're right that `ClockSkew` is a thing, but by default, `ASP.NET` has that set at 5 minutes... which will only be exceeded in some very rare circumstances. `ClockSkew` is usually only seconds off... so why mess with the default? – Serj Sagan Oct 02 '20 at 06:13
  • The 5 mn default value won't be taken into account with the code you provided because it overrides the lifetime validation logic of ASP.NET Core – disklosr Oct 03 '20 at 09:15
  • 3
    No, that's not accurate... unless specifically overriding, it is by default set to 5 minutes. https://stackoverflow.com/a/55155711/550975 – Serj Sagan Oct 03 '20 at 11:28
  • Have a look at the source code or do some tests for yourself. When you override lifetime validation, it's the equivalent of saying "I know what I'm doing", the framework doesn't do any extra validation regarding lifetime as it's now the developer's responsibility. – disklosr Oct 04 '20 at 15:56
  • You are wrong... thanks for wasting my time looking up the source code, but it is as I said... unless specifically overridden it is set to 5 minutes: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/7058653ca8e3fccc3bcab48f521d9b0b10060251/src/Microsoft.IdentityModel.Tokens/TokenValidationParameters.cs#L153 – Serj Sagan Oct 04 '20 at 23:01
  • I don't why you refuse to understand my point. I never said the default wasn't 5mn. I said the following: 1. Your answer doesn't add anh value as validation is already done in utc by the library. 2. Your answer doesn't take into account whatever value is set in ClockSkew, meaning that lifetime validation in your code only validates based on expiry time with no regard to ClockSkew. I hope it's clear for you now. Check out my answer it has a link to the relevant source code that shows how your code prevents ClockSkew from being taken into account. – disklosr Oct 06 '20 at 20:36
3

One solution is to validate the token without expiration time. This will return valid token even if the token had expired. Then in your code manually check the tokens expiration time. Here are snippets of the codes:

var validationParameters = new TokenValidationParameters()
{
   RequireExpirationTime = false,  // we can check manually
   ValidateIssuer = true,
   ValidateAudience = true,

   .
   .
   IssuerSigningKey = new SymmetricSecurityKey(symmetricKey)
};
            

Then when token is validated, check the expiration time with:

public bool IsExpired(DateTime now)
{
    return JwtSecurityToken.ValidTo < Date.UtcNow;
}

I hope this answer will help someone.

Said Al Souti
  • 305
  • 4
  • 13
3

It's already the case. The System.IdentityModel.Tokens.Jwt package does indeed validate JWT lifetime against UTC time. Here's the relevant bit from the source:

public static void ValidateLifetime(DateTime? notBefore, DateTime? expires, SecurityToken securityToken, TokenValidationParameters validationParameters)
        {
            ...

            if (notBefore.HasValue && expires.HasValue && (notBefore.Value > expires.Value))
                throw LogHelper.LogExceptionMessage(new SecurityTokenInvalidLifetimeException(LogHelper.FormatInvariant(LogMessages.IDX10224, notBefore.Value, expires.Value))
                { NotBefore = notBefore, Expires = expires });

            DateTime utcNow = DateTime.UtcNow;
            if (notBefore.HasValue && (notBefore.Value > DateTimeUtil.Add(utcNow, validationParameters.ClockSkew)))
                throw LogHelper.LogExceptionMessage(new SecurityTokenNotYetValidException(LogHelper.FormatInvariant(LogMessages.IDX10222, notBefore.Value, utcNow))
                    { NotBefore = notBefore.Value });
 
            if (expires.HasValue && (expires.Value < DateTimeUtil.Add(utcNow, validationParameters.ClockSkew.Negate())))
                throw LogHelper.LogExceptionMessage(new SecurityTokenExpiredException(LogHelper.FormatInvariant(LogMessages.IDX10223, expires.Value, utcNow))
                    { Expires = expires.Value });

            // if it reaches here, that means lifetime of the token is valid
            LogHelper.LogInformation(LogMessages.IDX10239);
        }

https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/blob/b5b7ed8fb8ce513469b51b87c5f76314783b74e3/src/Microsoft.IdentityModel.Tokens/Validators.cs#L268

disklosr
  • 1,536
  • 17
  • 26