JWT/Access token is not invalid when I think it should be (using client credentials flow)

I am new to auth0, and have been reading a bit and experimenting a bit. Let me just outline what we are trying to do

  1. We want to have a .NET WebApi (REST endpoint) that we use Auth0 to provide auth for
  2. We would like to call this NET WebApi (REST endpoint) from some other code we write (C# .NET code)

This seems to be a good fit for the “Client Credentials” flow as described here : Client Credentials Flow

So what I did was the following

  • I have registered our Custom API in the “API Details” portal and downloaded a sample app from Auth0 website “WebAPIApplication” which makes use of JWT token
  • I have also seen that a machine to machine application was created
  • I then went into the Custom API and grabbed the Domain/ApiIdentifier and updated the settings in my .NET WebApi (REST endpoint) project to use that
  • I then used some code to get the token based on the C# code in the “test” tab of my API project
  • This was able to give me an access token, which I could then use to test my .NET WebApi (REST endpoint)

All good so far

  • I then went into API Settings, and change the Token Expiry to 5 seconds. I made sure this was reflected in the code that fetches the token from Auth0. It was could clearly see it had expires_in = 5.

Which I obtained using this C# code

var client = new RestClient("https://dev-68vt-2k4.eu.auth0.com/oauth/token");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\"client_id\":\"Oi5lEI......R8LEgW\",\"client_secret\":\"mI3...........8Cj6huSpUp\",\"audience\":\"XXXXXX\",\"grant_type\":\"client_credentials\"}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);

Where you can see that the token I got from the following code reflects the 5 seconds expiry

  • So I waited about 1 minute (longer than 5 seconds) and used the token to hit my .NET WebApi (REST endpoint) and it still worked

This surprised me a lot. Why is this happening?

The information provided although very detailed (thanks for that) is insufficient for a definitive response as from the screenshot we would assume the token part is correct in terms that the lifetime is indeed 5 seconds.

However, there may be an issue on the API side that results in the token being incorrectly validated or not validated at all.

You should try a few things:

  • check that the date/time settings of the server running the API; you may have been really unlucky and got an out of sync clock that allows the token to be valid after the one minute wait of it being generated.
  • check what happens if you don’t send any token in the API request.
  • check what happens if you send a JWT you grabbed from an example online that has nothing to do with Auth0.

The above may provide additional information and some hints to troubleshoot further.

So the example code I am using was this one directly from authO : aspnet-core-webapi-01-authorization.zip, so I am assuming that an official auth0 sample would be done correctly. You basically get the demo code suggested to you when you create a new app in the auth0 portal

I tried a few of the things you suggested

  • I am running the API locally so there is just one system clock at play, my own one, localhost, which is what would be used for the API. I guess the auth0 server time that generates the token could be off my own, but we dont typically suffer from clock drift
  • If I send no token its unauthorised
  • If I send a bad token its unauthorised

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
    };
});
2 Likes