10
votes

I am creating my own custom authentication on ASP. Net MobileService deployed on Azure. I use JWT tokens. Here is how I generate a new token (claimType = email):

    public static string GetSecurityToken(String email)
    {
        var symmetricKey = Convert.FromBase64String(signingKey);
        var tokenHandler = new JwtSecurityTokenHandler();

        var now = DateTime.UtcNow;
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
                    {
                    new Claim(ClaimTypes.Email, email)
                }),
            NotBefore = now,
            Expires = now.AddYears(10),
            Issuer = issuer,
            Audience = audience,
            IssuedAt = now,
            SigningCredentials = new SigningCredentials(
                new SymmetricSecurityKey(symmetricKey),
                SecurityAlgorithms.HmacSha256Signature),
        };

        var stoken = tokenHandler.CreateToken(tokenDescriptor);
        var token = tokenHandler.WriteToken(stoken);

        return token;
    }

The token is sent to the client and stored. But when I try to authorize a message based on its token, I get the error:

Lifetime validation failed. The token is missing an Expiration Time.

This is how I try to validate the token:

    public static ClaimsPrincipal GetPrincipal(string token)
    {
        try
        {
            var tokenHandler = new JwtSecurityTokenHandler();                
            var jwtToken = tokenHandler.ReadToken(token) as JwtSecurityToken;
            
            if (jwtToken == null)
                return null;

            var symmetricKey = Convert.FromBase64String(signingKey);

            Debug.WriteLine(String.Format("JWTManager > GetPrincipal > Validating Token: {0}", token));
            foreach (Claim claim in jwtToken.Claims)
            {
                Debug.WriteLine(String.Format("JWTManager > GetPrincipal > Claims: {0}", claim.ToString()));
            }
            
            var validationParameters = new TokenValidationParameters()
            {
                //RequireExpirationTime = true,
                //ValidateLifetime = true,
                ValidateIssuer = true,
                ValidateAudience = true,
                IssuerSigningKey = new SymmetricSecurityKey(symmetricKey),
            };
            
            SecurityToken securityToken;
            var principal = tokenHandler.ValidateToken(token, validationParameters, out securityToken);
            if (principal != null)
                Debug.WriteLine(String.Format("JWTManager > GetPrincipal > Principal: {0}", principal));
            return principal;
        }
        catch (SecurityTokenException ex)
        {
            Debug.WriteLine(String.Format("JWTManager > GetPrincipal: {0}", ex.Message));
            return null;
        }
        catch (Exception ex)
        {
            Debug.WriteLine(String.Format("JWTManager > GetPrincipal: {0}", ex.Message));
            return null;
        }
    }

Exception is thrown on executing tokenHandler.ValidateToken and null is returned to principal.

My assumption is that maybe I am not setting the Expires and Issuers properties correctly and the TokenHanlder fails to validate them. However, when I check the jwtToken, all the claims are correctly set.

Here is the complete debug output:

JWTManager > GetPrincipal > Claims: email: [email protected]

JWTManager > GetPrincipal > Claims: nbf: 1494752301

JWTManager > GetPrincipal > Claims: exp: 33051661101

JWTManager > GetPrincipal > Claims: iat: 1494752301

JWTManager > GetPrincipal > Claims: iss: MASKED

JWTManager > GetPrincipal > Claims: aud: MAKSED

JWTManager > GetPrincipal: IDX10225: Lifetime validation failed. The token is missing an Expiration Time. Application: Tokentype:

2
I think there's something wrong with your EXP calculation, when I convert it, the result is Timestamp Converter 33051661101 Is equivalent to: 05/14/3017 @ 8:58am (UTC) (result from unixtimestamp.com/index.php). Maybe the validation doesn't accept a value of 1000 yrs in the future?jps
Interesting. So I should switch now.AddYears(10) to something else. Hmmm... can't think of anything. Instead of adding 10 years, it added 1000 years.AHA
take a look at my anser there: stackoverflow.com/questions/43593074/jwt-validation-fails/… it explains how the timestamp is defined.jps
Thanks. It worked.AHA

2 Answers

26
votes

@aha, it looks like you solved your problem by shortening your expiration datetime to just one year in the future. Which works, but if you want to understand the underlying architectural reason why it was failing before (and as such make appropriate architectural changes in your own code), you can read this SO post here: https://stackoverflow.com/a/46654832/1222775.

The bottom line is that expiration date for JWTs validated against the Microsoft owin middle ware have an upper limit of 2147483647 (which also happens to be Int32.MaxValue), and that translates to: Tue, 19 Jan 2038 03:14:07 GMT

In your SO question, the debug output you posted showing the "exp" claim value you used, was a value of 33051661101 which translates to: Wednesday, May 14, 3017 8:58:21 AM which blows past Microsoft's upper limit for the exp value by almost 80 years :).

I hope Microsoft will solve this issue soon, but in the mean time for anyone experiencing a similar issue, try no to issue too long lasting tokens, or at least, don't make it go past Tue, 19 Jan 2038 @ 03:14:07 GMT :).

4
votes

Following the suggestion here, I fixed the problem by switching from using

System.IdentityModel.Tokens.Jwt.TokenHandler.CreateToken(SecurityTokenDescriptor)

to

new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(JwtHeader, JwtPayload).

And defined the payload as follows:

DateTime centuryBegin = new DateTime(1970, 1, 1);
var exp = new TimeSpan(DateTime.Now.AddYears(1).Ticks - centuryBegin.Ticks).TotalSeconds;
var now = new TimeSpan(DateTime.Now.Ticks - centuryBegin.Ticks).TotalSeconds;
var payload = new System.IdentityModel.Tokens.Jwt.JwtPayload
{
    {"iss", issuer},
    {"aud", audience},
    {"iat", (long)now},
    {"exp", (long)exp}
};

So, I ended up not using the SecurityTokenDescriptor class because it expects DateTime objects to be assigned to Expirs and IssuedAt, or Lifetime properties (depending on whether it is in the Microsoft.IdentityModel.Tokens or System.IdentityModel.Tokens namespace).

I have no intention of using SecurityTokenDescriptor; however, I couldn't find a solution on how to use SecurityTokenDescriptor and still set correct values to the "exp" field.