Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JWT Validation fails

Tags:

c#

jwt

I have created a JwtToken by setting up the Payload as shown below

var payload = new JwtPayload
        {
            {"aud", "wellmark.com" },
            {"iss", "wellmark" },
            {"iat", DateTime.Now.Ticks },
            {"exp", DateTime.Now.AddDays(90).Ticks },
        };

The reason I had to use ticks is because that is the only way to get an integer value for the issued at and expiration times. I agree that ticks is in fact a long and not an int, but that was the only way.

Now when I come back to validate the token, I am doing the below

var tokenValidationParams = new TokenValidationParameters
            {
                IssuerSigningKey = new X509SecurityKey(jwtCert),
                ValidAudience = "masked",
                ValidIssuer = "masked",
                IssuerSigningKeyResolver = (string token, Microsoft.IdentityModel.Tokens.SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) => new List<X509SecurityKey> { new X509SecurityKey(jwtCert) }
            };

            tokenHandler.ValidateToken(id, tokenValidationParams
                   , out validatedToken);

It is however failing saying

Lifetime validation failed. The token is missing an Expiration Time. Tokentype: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'.

This is most likely because the validation method is trying to convert the long to an int and because it is unable to convert it, it simply returns a null as indicated in the documentation shown here.

Has anyone had success with this mechanism. Please note that I am using X509 Certificate to Sign my Jwt.

Narasimham

like image 545
avsln Avatar asked Apr 24 '17 16:04

avsln


1 Answers

Another point worth noting is that though @jps answer is actually technically correct, it really doesn't explain why you got the exception: Lifetime validation failed. The token is missing an Expiration Time. Tokentype: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'; why would it be suggesting that you are missing an expiration time, even though you have clearly provided one as an Int64 (long)?. Theoretically any Int64 number should represent some point in time in the future, even when taking into account epcoh time constraints of starting from January 1st 1970 (https://www.rfc-editor.org/rfc/rfc7519#section-2).

The true underlying problem is the fact that the number you are using when you use Ticks is a spectacularly large number; for one, Microsoft defines it as:

the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001 (0:00:00 UTC on January 1, 0001, in the Gregorian calendar. https://msdn.microsoft.com/en-us/library/system.datetime.ticks(v=vs.110).aspx

Two things to keep in mind with the above:

  1. It goes all the way back to January 1, 0001
  2. It is based on Nano seconds, and epoch time is based on milliseconds (a factor of 1000000).

All of that said, this should in theory still represent some point in time in the future, albeit super-far in the future.

So why is it not working -- when it really should!?

The underlying reason why you actually do end up with the exception mentioned in your SO ticket is actually NOT that you are using Ticks (instead of an epoch timestamp), but rather because Microsoft has a theoretical upper limit for validation of JWTs when using their frameworks and Owin middleware (more on that later, keep reading).

One would hope that:

  1. Such an upper limit really shouldn't be imposed; but perhaps there are security use cases that necessitates such an imposed upper limit on the expiration date for tickets - I can't really think of one at the moment.

  2. Bullet #1 above notwithstanding, one would hope that the said upper limit is really far out in the future (Extra credit bonus points if you are both a millennial reading this and have heard/know of the Y2K bug?). However the limit set by the MS team is actually around the corner: January 19, 2038 @ 03:14:07 GMT

You can test it for yourself by trying to validate the following token with an exp value set for January 20th 2038 (2147558400).

{
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "SampleToken",
  "nbf": 1507578537,
  "exp": 2147558400,  //<== This timestamp will not work :/
  "iss": "https://api.example.com",
  "aud": "https://example.com"
}

It fails validation with a

Microsoft.IdentityModel.Tokens.SecurityTokenException and an Exception Message of: IDX10225: Lifetime validation failed. The token is missing an Expiration Time

Which is NOT true, I have provided an expiration date, and it is a valid epoch timestamp (and not an outrageously large number represented by DateTime.Ticks ;).

If you repeat the same test, by trying to validate the following token with an exp value set for January 19th 2038 00:00:00 (2147472000) -- which happens to be before the theoretical limit in the current MSFT libraries of January 19, 2038 @ 03:14:07 GMT, it works.

{
  "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": "SampleToken",
  "nbf": 1507578537,
  "exp": 2147472000,
  "iss": "https://api.example.com",
  "aud": "https://example.com"
}

Conclusions:

  1. Microsoft either needs to:

a. Extend the max date for expiration values for tokens

b. OR, add another Exception message for the cases when the exp value exceeds the current max - perhaps IDX10226: Liftetime validation not possible. Expiration date set too far in the future.

Bonus-points

Ok, after going through such detail to point out the upper limit of the exp claim, it will be a shame to not explain why there is an upper limit to begin with. Some reading this would have already spotted the reason for the upper limit as I have dropped clues through out the writeup; If you caught-on to it already, Kudos to you, if you haven't here is why: Microsoft is implicitly using an integer or (Int32) when doing comparisons of the values for the exp claim epoch timestamp for the "exp" claim. I would like to think of this as an oversight (someone probably didn't think through the architecture fully and perhaps didn't consider the implications of using (explicitly or implicitly) the Int32 vs the long data type (Int64) to store/compare epoch timestamps. It strikes me as silly that this was an actual architectural decision to choose Int32 as the data type to interact with epoch timestamps for their library -- but then again, like I said earlier in this write-up, perhaps there are security use cases that necessitates such an imposed upper limit (I just can't for the life of me think of one at the moment).

Still not convinced, here is the maximum value an Int32 can hold: 2147483647, according to Microsoft: https://msdn.microsoft.com/en-us/library/system.int32.maxvalue(v=vs.110).aspx. Assuming that 2147483647 is an epoch timestamp, guess what GMT date/time you get when convert 2147483647 into a date? Yup you do get: January 19th 2038 03:14:07 GMT. You can use https://www.epochconverter.com for conversion convenience.

Workarounds

  1. The most obvious one is do not create tokens that expire at any point after the epoch timestamp of 2147483647. It just can't be validated by Microsoft's owin middle-ware at the current time of this writing (Hopefully they fix this soon).

  2. Another workaround is you write your own token Lifetime validation logic. It is not as complex as one would imagine, but it does seriously help to have an understanding of the underlying Owin middle-ware, and JW* (JWT, JWE, JWS, JWK, etc. Michael B. Jones has an interesting writeup about JW* here: http://www.niso.org/apps/group_public/download.php/14003/SP_Jones_JSON_isqv26no3.pdf)

like image 50
Awah Teh Avatar answered Sep 23 '22 15:09

Awah Teh