Reputation: 978
I upgraded my app from .NET Core 2.2 to .NET Core 3.1. When I tried to test out my endpoints with PostMan I noticed I was getting 401 Unauth Error. When I look at the header I am seeing that the expiry time is invalid:
I took the following bearer token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiQm9iIiwibmJmIjoiMTYxNzk3Nzg1MSIsImV4cCI6IjE2MjMxNjE4NTYiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsiQmFza2V0YmFsbCIsIlJ1Z2J5IiwiRm9vdGJhbGwiXX0.QRLuXFeopf7QZ1NUzWcctuSfnNXiPgc2UH7NxAuHYvw
that I am getting on my generate token endpoint and decoded it with jwt.io and the exp field is "1623161856".
I converted that to a date object in Javascript and it equals 60 days in the future.
So token certainly is not expired. Not sure if I missing anything in the upgrade to .NET Core 3.1 but here is the relevant code:
In Startup.cs
I have
public void ConfigureServices(IServiceCollection services)
{
// Initial Setup
services.AddMvc();
services.AddSingleton<IConfiguration>(Configuration);
// Call this in case you need aspnet-user-authtype/aspnet-user-identity
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Register the Swagger generator, defining one or more Swagger documents
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(Configuration["v1"], new OpenApiInfo { Title = Configuration["Sports"], Version = Configuration["v1] });
});
services.AddDataProtection();
//Authentication Setup
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("AnythingYouWant")),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
options.SaveToken = true;
options.Events = new JwtBearerEvents()
{
OnTokenValidated = context =>
{
var accessToken = context.SecurityToken as JwtSecurityToken;
if (accessToken != null)
{
ClaimsIdentity identity = context.Principal.Identity as ClaimsIdentity;
if (identity != null)
{
identity.AddClaim(new Claim("access_token", accessToken.RawData));
}
}
return Task.CompletedTask;
}
};
});
services.AddAuthorization();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/" + Configuration["v1"] + "/swagger.json", Configuration["Sports"]);
});
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
endpoints.MapControllerRoute("swagger", "swagger/");
});
app.UseWelcomePage("/swagger");
}
And the token is generated by one of my api endpoints. That code is as shown:
[HttpPost("SportApi/Token")]
[ServiceFilter(typeof(SportResourceFilter))]
public IActionResult Create(string key)
{
return new ObjectResult(GenerateToken(key));
}
private string GenerateToken(string someKey)
{
JwtSecurityToken token = new JwtSecurityToken();
List<SportAPIKey> ro = new List<SportAPIKey>();
if (!string.IsNullOrEmpty(someKey))
{
using (StreamReader r = new StreamReader("keys.json"))
{
string json = r.ReadToEnd();
ro = JsonConvert.DeserializeObject<List<SportAPIKey>>(json);
}
if (ro.Exists(sak => sak.SportAPIKeyValue.Equals(someKey)))
{
SportAPIKey sportapikey = ro.Find(sak => sak.SportAPIKeyValue.Equals(someKey));
List<Claim> lc = new List<Claim>();
Claim claimClient = new Claim(ClaimTypes.Name, sportapikey.Client);
lc.Add(claimClient);
foreach (string team in sportapikey.Teams)
{
lc.Add(new Claim(ClaimTypes.System, team.Trim()));
}
Claim claimEffDate = new Claim(JwtRegisteredClaimNames.Nbf, new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds().ToString());
lc.Add(claimEffDate);
int tokenLifespan = 60;
Claim claimExpDate = new Claim(JwtRegisteredClaimNames.Exp, new DateTimeOffset(DateTime.Now.AddDays(tokenLifespan)).ToUnixTimeSeconds().ToString());
lc.Add(claimExpDate);
foreach (string sport in sportapikey.Sports.Split(","))
{
lc.Add(new Claim(ClaimTypes.Role, sport.Trim()));
}
var claims = lc.ToArray();
token = new JwtSecurityToken(
new JwtHeader(new SigningCredentials(
new SymmetricSecurityKey(Encoding.UTF8.GetBytes("AnythingYouWant")),
SecurityAlgorithms.HmacSha256)),
new JwtPayload(claims));
}
}
return new JwtSecurityTokenHandler().WriteToken(token);
}
Upvotes: 2
Views: 1470
Reputation: 22555
Your token contains the timestamps nbf
and exp
as string:
"nbf": "1617977851",
"exp": "1623161856",
which is invalid. On https:\jwt.io you can see that something is wrong when you hover with your mouse over these values. Ususally it shows the timestamp, but in your example it shows: "Invalid date":
Timestamps in a JWT are supposed to be numerical values:
NumericDate
A JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time
The values itself are correct:
The error happens during generation of the claims:
Claim claimExpDate = new Claim(JwtRegisteredClaimNames.Exp,
new DateTimeOffset(DateTime.Now.AddDays(tokenLifespan)).ToUnixTimeSeconds().ToString());
lc.Add(claimExpDate);
Instead use this constructor, which lets you set the ClaimValueType:
new Claim(JwtRegisteredClaimNames.Exp,
new DateTimeOffset(DateTime.Now.AddDays(tokenLifespan)).ToUnixTimeSeconds().ToString(),
ClaimValueTypes.Integer64)
Or even better, let the framework add the expiration timestamp for you, using this constructor:
public JwtSecurityToken (string issuer = default, string audience = default, System.Collections.Generic.IEnumerable<System.Security.Claims.Claim> claims = default, DateTime? notBefore = default, DateTime? expires = default, Microsoft.IdentityModel.Tokens.SigningCredentials signingCredentials = default);
e.g.:
JwtSecurityToken(expires: DateTime.UtcNow.AddDays(60), ...)
Upvotes: 5