Caching JWKS signing key

This doc states:

Currently Auth0 only supports a single JWK for signing, however it is important to assume this endpoint could contain multiple JWKs. As an example, multiple keys can be found in the JWKS when rotating signing certificates.

  1. typically, when does a rotation of the signing certificates happen? who/what triggers it?

  2. when a rotation happen, are all new tokens signed with that new certificate or is there some kind of delay before being in use?

I am trying to avoid fetching the certificate when new tokens kid are submitted to my app, and instead have a separate process that will refresh a cache of active keys asynchronously. But for this to work I need to fetch the signing certificate before my app needs it. Any tips on how to do this would be greatly appreciated.

Thanks!

2 Likes

Hi @benji.

(This response was updated on April 2021 to reflect signing keys rotation and updated guidance on caching).

Auth0 nows support signing key rotation initiated by tenant admins (see Manage Signing Keys for details). A tenant’s JWKS resource will have the current key and the “next” key, so applications that prepare in advance for a key rotation.

The JWKS resource will return the same set of keys most of the time. Applications should cache these resources, but they also need to be prepared to handle signing key rotations:

  • Tenant admin triggers the rotation of the tenant certificate manually (adding a new one in the list, and making it “active” at a later time). This is a prepared event, applications will see the “next” key before it is used to sign tokens, and switching to the new key will be handled gracefully.

  • A private key is compromised and a rotation is needed immediately. Auth0 would rotate keys and notify affected customers.

An application would need to decide on a cache lifetime that strikes a good balance between performance and security, particularly for the emergency case where a private key is compromised. In addition to fetching a new JKWS every so often (e.g. 1 hour), you might want to:

  • Try to fetch new keys if you can’t find a kid referenced in a token in the cached JWKS
  • Put some protection to avoid trying to refresh the JWKS resource constantly (someone could try a DDOS attack by sending a token with a non-existing kid). E.g. “Wait at least 5 minutes before trying to get a new JWKS”.

As an example, Microsoft’s ConfigurationManager class for .Net Core 6 has a 12h cache lifetime by default, and sets 5 minutes as the minimum time span that must pass between attempts to refresh the JWKS:

1 Like

Hi @nicolas_sabena, thank you for your answer.

Note that I’m more interested in some form of notification (event) of the rotation of the keys rather than being able to control/force it.

With the current implementation of the asp.net core framework (and many other), an attacker spamming unrecognized signatures/KIDs will trigger as many outbound requests to refresh the certificate(s) every time. This is not ideal in terms of performance and cost.

Some frameworks have the options of rate limiting (like auth0’s node-jwks-rsa). But then if the attacker spams unrecognized signatures/KIDs and rate-lock the refreshing of the keys while a key is being compromised/rotated, then the keys won’t be refreshed for some time (by default up to 10 minutes). This means that valid tokens won’t be validated during that time frame. Depending on the app this can be more or less of an inconvenience.

Hi,

The F5 Oauth client handles key rotation by referencing the KID and it can be configured to check the keys endpoint at regular intervals for additional keys. I had this working with Okta however I’m into this bug listed here. I will raise a case with F5 as it looks like a standards compliance issue with there code. I am wanting to know if I can resolve this by modifying the auth0 configuration.

Currently I have configured the keys manually as a work around but if the keys get rotated it is going to break.

Okta just publish the kid, modulus and public exponent in there key endpoint. I’ve removed the X5T and X5C from the auth0 key to get it to parse on the F5 client as follows.

apm oauth jwk-config jwk_arcinfra.au.auth0.com_provider1 {
alg-type RS256
key-id REVGQ0QwOTJGMTA4MDBCNDlFQjI0NTAxREZGMzY1QzAwQ0E4REU5RA
modulus nGpHZs0DOWJkwcOq6LBJYh1IOSjT1t_9yL8SbrK0VkdLwlmSAxdNKT478-8yXnbclvJieIZwd-lEvbAeoUsEJbwZSM9BRyGtDp4nUN8HS1POFccdAwEr9zk_VAmZA42h5Zd8EsN82HTXL8CRJol9v-Z9FzRzqWRZW9EEnB9zEClTF8kiaYheMWxAqVl_5PYnFoUMjJV9OInrHFIe2Cy1t2mdwrTSh4rC7hhB9pD8BqrkY9Y01-ZT6-_Q-KtXSzLaqXhiWbRTGk56PsLKVAbSpl0-nYRHgfpBG97zGrs9NjmOoBnKcBjJzgssUicvvJ5R6Ebi-_S1_MTGF4NnHZA5Cw
public-exponent AQAB
shared-secret-enc-format none
}

This is what is comming from the endpoint by default.

https://arcinfra.au.auth0.com/.well-known/jwks.json

alg “RS256”
kty “RSA”
use “sig”
x5c
0 “MIIDBTCCAe2gAwIBAgIJHA8VeLs33cB4MA0GCSqGSIb3DQEBCwUAMCAxHjAcBgNVBAMTFWFyY2luZnJhLmF1LmF1dGgwLmNvbTAeFw0xOTAzMjEwMzQ3MTdaFw0zMjExMjcwMzQ3MTdaMCAxHjAcBgNVBAMTFWFyY2luZnJhLmF1LmF1dGgwLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJxqR2bNAzliZMHDquiwSWIdSDko09bf/ci/Em6ytFZHS8JZkgMXTSk+O/PvMl523JbyYniGcHfpRL2wHqFLBCW8GUjPQUchrQ6eJ1DfB0tTzhXHHQMBK/c5P1QJmQONoeWXfBLDfNh01y/AkSaJfb/mfRc0c6lkWVvRBJwfcxApUxfJImmIXjFsQKlZf+T2JxaFDIyVfTiJ6xxSHtgstbdpncK00oeKwu4YQfaQ/Aaq5GPWNNfmU+vv0PirV0sy2ql4Ylm0UxpOej7CylQG0qZdPp2ER4H6QRve8xq7PTY5jqAZynAYyc4LLFInL7yeUehG4vv0tfzExheDZx2QOQsCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvxKllUomKBVqx3C4npUHV1My/2gwDgYDVR0PAQH/BAQDAgKEMA0GCSqGSIb3DQEBCwUAA4IBAQBmnnIwgX9p9rN3YzREY98PPWgG/b3VDqgyXrchG94eaKn5KWbU6h8rEi/bWbVX6Yp5R/pMFjv9h/+gbISBY8hz+VC4cEtJdf83VbQo0y7V5LYYfuRad8stT9woN1GKwtB+yq1GGcEJ+ZUyvIrMK88hTTRRFaRgaEkJfLChuiku2Xn7l8QvrxNokyBT1G/04IeQ5sbtzl3G1mQL1nE9I+WoAelBHQzOsNUBFRuMNiaonlOvd/zVaAQccReYMn8UMn+Z0PYCEJq9gR+kEp8groRHvEg4ZmPahjI2kjudAqFlbb80Et+a/Ufd/L5TM4VEY1AASnm9ZpQqCn47Z+Gkfvmw”
n “nGpHZs0DOWJkwcOq6LBJYh1IOSjT1t_9yL8SbrK0VkdLwlmSAxdNKT478-8yXnbclvJieIZwd-lEvbAeoUsEJbwZSM9BRyGtDp4nUN8HS1POFccdAwEr9zk_VAmZA42h5Zd8EsN82HTXL8CRJol9v-Z9FzRzqWRZW9EEnB9zEClTF8kiaYheMWxAqVl_5PYnFoUMjJV9OInrHFIe2Cy1t2mdwrTSh4rC7hhB9pD8BqrkY9Y01-ZT6-_Q-KtXSzLaqXhiWbRTGk56PsLKVAbSpl0-nYRHgfpBG97zGrs9NjmOoBnKcBjJzgssUicvvJ5R6Ebi-_S1_MTGF4NnHZA5Cw”
e “AQAB”
kid “REVGQ0QwOTJGMTA4MDBCNDlFQjI0NTAxREZGMzY1QzAwQ0E4REU5RA”
x5t “REVGQ0QwOTJGMTA4MDBCNDlFQjI0NTAxREZGMzY1QzAwQ0E4REU5RA”

This fails with the default Auth0 /.well-known/openid-configuration and /.well-known/jwks.json content.

The x5t value in /.well-known/jwks.json is correct but BIGIP fails to parse/handle it against the certificate content.

The reason it does this is because the BIGIP implementation is expecting the X5T value to include base64 padding characters (’=’ characters) which MUST NOT be used in the context of JWS’s. This is why Auth0 does not have ‘=’ characters in KID/X5T; they conform to the RFC spec.

BIGIP JWKS consumption implementation needs a way in which to validate X5T without requiring padding characters to exist, e.g. calculate base64 KID/X5T and remove ‘=’ characters prior to comparison.

Hey there!

Sorry for such huge delay in response! We’re doing our best in providing you with best developer support experience out there, but sometimes our bandwidth is not enough comparing to the number of incoming questions.

Wanted to reach out to know if you still require further assistance?