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: testEmail@email.com
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:
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.