After much messing about I found the answer for this. The answer is actually very simple, but I will also include all my code here in case anyone finds it useful
TLDR : There is a default clock skew value on the TokenValidationParameters (TokenValidationParameters.DefaultClockSkew Field (Microsoft.IdentityModel.Tokens) | Microsoft Learn) which is 5 minutes.
So you need to change that for testing purposes, which you can do with this sort of thing
ClockSkew = TimeSpan.Zero
I wanted to make sure everything was 100% in what I was saying, so I did the following
verifying using the public key
-
In a new Console app, I just wanted to try out the JWT validation without any ASP MVC/WebApi stuff. Just pure Console app logic
-
So I read about this route that has the public key https://[YOUR_DOMAIN].auth0.com/pem
-
I then used that to get the PEM file saved it to a file
-
I then created this code to parse and verify using the public key data (I used BouncyCastle.NetCore)
private static bool ValidateToken(string authToken)
{
//for extra debugging info
//IdentityModelEventSource.ShowPII = true;
var validationParameters = GetValidationParameters();
SecurityToken validatedToken;
JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
var user = handler.ValidateToken(authToken, validationParameters, out validatedToken);
var validFrom = validatedToken.ValidFrom;
var validTo = validatedToken.ValidTo;
return true;
}
private static TokenValidationParameters GetValidationParameters()
{
const string validIssuer = “https://[YOUR_DOMAIN].auth0.com/”; // Your Auth0 domain
const string validAudience = “XXXXX”; // Your API Identifier
var rsa = PublicObjectKeyFromPemFile(@"C:\Users\s_barber\Downloads\aspnet-core-webapi-01-authorization\quickstart\ConsoleApp1\PublicKey.pem");
return new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = validIssuer,
IssuerSigningKey = new RsaSecurityKey(rsa),
ValidAudiences = new[] { validAudience },
ClockSkew = TimeSpan.Zero
};
}
public static RSACryptoServiceProvider PublicObjectKeyFromPemFile(String filePath)
{
using (TextReader publicKeyTextReader = new StringReader(File.ReadAllText(filePath)))
{
Org.BouncyCastle.X509.X509Certificate x509Certificate = (Org.BouncyCastle.X509.X509Certificate)new PemReader(publicKeyTextReader).ReadObject();
RsaKeyParameters bouncyParams = (RsaKeyParameters)x509Certificate.GetPublicKey();
RSACryptoServiceProvider cryptoServiceProvider = new RSACryptoServiceProvider();
RSAParameters rsaParams = new RSAParameters();
rsaParams.Modulus = bouncyParams.Modulus.ToByteArrayUnsigned();
rsaParams.Exponent = bouncyParams.Exponent.ToByteArrayUnsigned();
cryptoServiceProvider.ImportParameters(rsaParams);
return cryptoServiceProvider;
}
}
This enabled me to double check that the token was indeed coming back with expiry of 5 seconds. I also added in the following line inside the GetValidationParameters() and then I could see that the token was indeed being treated as invalid due to the expiry, where I saw a SecurityTokenExpiredException:
private static TokenValidationParameters GetValidationParameters()
{
const string validIssuer = "https://[YOUR_DOMAIN].auth0.com/"; // Your Auth0 domain
const string validAudience = "XXXXX"; // Your API Identifier
var rsa = PublicObjectKeyFromPemFile(@"..\PublicKey.pem");
return new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = true,
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidIssuer = validIssuer,
IssuerSigningKey = new RsaSecurityKey(rsa),
ValidAudiences = new[] { validAudience },
//THIS IS THE NEW LINE
//THIS IS THE NEW LINE
//THIS IS THE NEW LINE
//THIS IS THE NEW LINE
ClockSkew = TimeSpan.Zero
};
}
Ok cool works.
WebApi .NET Core Project
So now what I did was just turned my attention to the .NET Core Web Api project.
All I had to do was add a bit more into what came with the Autho template, and beef up the validation parameters a bit more
.AddJwtBearer(options =>
{
options.Authority = domain;
options.Audience = Configuration["Auth0:ApiIdentifier"];
//new stuff I added
options.RequireHttpsMetadata = false;
options.SaveToken = false;
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuerSigningKey = false,
ValidateLifetime = true,
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
};
});