I'm working on an ASP.NET Core 8.0 Web API project with a layered architecture. I've encountered an issue during the JWT validation process in my authentication and registration endpoints.
In my program.cs
, I have the following token validation parameters set up:
var tokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidAudience = jwtSettings.ValidAudience,
ValidIssuer = jwtSettings.ValidIssuer,
IssuerSigningKey = new
SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.Secret)),
ClockSkew = jwtSettings.TokenLifetime
};
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = tokenValidationParameters;
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["authorization"];
return Task.CompletedTask;
}
};
});
builder.Services.AddSingleton(tokenValidationParameters);
For token generation, I use the following method:
public async Task<Response<RefreshTokenDto>> GenerateAuthResultForCustomAsync(Customer
customer)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_jwtSettings.Secret);
var userRoles = await _userManager.GetRolesAsync(customer);
var authClaims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Sub, customer.Email),
new Claim(JwtRegisteredClaimNames.Jti,
Guid.NewGuid().ToString()),
new Claim("customerId", customer.Id),
new Claim("firstName", customer.FirstName),
new Claim("lastName", customer.LastName),
new Claim("countryId", customer.CountryId.ToString()),
new Claim("phoneNumber", customer.PhoneNumber),
new Claim("userName", customer.UserName)
};
authClaims.AddRange(userRoles.Select(role => new Claim(ClaimTypes.Role, role)));
var tokenDescriptor = new SecurityTokenDescriptor()
{
Subject = new ClaimsIdentity(authClaims),
Issuer = _jwtSettings.ValidIssuer,
Audience = _jwtSettings.ValidAudience,
Expires = _dateTimeProvider.Now.Add(_jwtSettings.TokenLifetime).UtcDateTime,
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key),
SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
}
Lastly, the method that validates the token is:
public class PrincipalTokenService : IPrincipalTokenService
{
private readonly TokenValidationParameters _tokenValidationParameters;
public PrincipalTokenService(TokenValidationParameters tokenValidationParameters)
{
_tokenValidationParameters = tokenValidationParameters;
}
public ClaimsPrincipal GetPrincipalFromToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
var handler = tokenHandler.ValidateToken(
token,
_tokenValidationParameters,
out var validatedToken);
return !IsJwtWithValid(validatedToken) ? null : handler;
}
catch
{
return null;
}
}
}
When executing GetPrincipalFromToken
, I get the following error:
Microsoft.IdentityModel.Tokens.SecurityTokenNoExpirationException: 'IDX10225: Lifetime validation failed. The token is missing an Expiration Time. Tokentype: 'System.IdentityModel.Tokens.Jwt.JwtSecurityToken'
However, removing the expiration date from the token and disabling its validation in the configuration leads to similar issues with the issuer, and subsequently with the audience. Despite these validations, the generated token is valid as confirmed by jwt.io.
The entire token generation and validation logic was ported from a working project in ASP.NET Core 6.0, which has been functioning without issues for over 3 years. The server environment and configuration have remained unchanged, suggesting the code itself is not the issue.
I suspect the problem might be related to package versions. Here are the relevant packages used in the 8.0 project:
<PackageReference Include="Asp.Versioning.Mvc" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3">
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="7.4.1 />
Has anyone else encountered similar issues with JWT validation in ASP.NET Core 8.0? Any insights or suggestions would be greatly appreciated.
P.S. Additionally, I'd like to clarify that the jwtSettings is a model used to populate the tokenValidationParameters with data from the application settings. The generated token, when verified on jwt.io, appears correct in all respects - the exp field contains the correct time, the iss field is present, and all the data within the token is accurate. I've even attempted to bypass the jwtSettings model entirely by hardcoding the parameters directly into tokenValidationParameters, but I encounter the same issue, which leads me to suspect that the problem might be within the package handling ValidateToken itself.
Microsoft.IdentityModel.Tokens
, creating a newSymmetricSecurityKey
stops working. This is because SymmetricSecurityKey is part of that library. – Interceptor