Repository: auth0/auth0-python
Pull request #490 (2023-05-01 11:21:06)

Add cache_ttl param to AsymmetricSignatureVerifier
### Changes

This PR adds the parameter `cache_ttl` to `AsymmetricSignatureVerifier`. This new, optional parameter allows setting the cache TTL for the underlying `JwksFetcher`. This allows caching the JWK set for more (or less) time than the default 600 seconds.

`AsymmetricSignatureVerifier` had to be moved below `JwksFetcher` because it now references it, so the latter has to be defined earlier in the file.

This closes [#489](https://github.com/auth0/auth0-python/issues/489).

### References

- #489 

### Testing

- [x] This change adds unit test coverage
- [ ] This change adds integration test coverage
- [x] This change has been tested on the latest version of the platform/language (Python `3.11.2`)

### Checklist

- [x] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [x] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
- [x] All existing and new tests complete without errors



Patch:
diff --git a/auth0/authentication/token_verifier.py b/auth0/authentication/token_verifier.py
index 9c9b51f..08331ef 100644
--- a/auth0/authentication/token_verifier.py
+++ b/auth0/authentication/token_verifier.py
@@ -126,22 +126,6 @@ class SymmetricSignatureVerifier(SignatureVerifier):
         return self._shared_secret
 
 
-class AsymmetricSignatureVerifier(SignatureVerifier):
-    """Verifier for RSA signatures, which rely on public key certificates.
-
-    Args:
-        jwks_url (str): The url where the JWK set is located.
-        algorithm (str, optional): The expected signing algorithm. Defaults to "RS256".
-    """
-
-    def __init__(self, jwks_url, algorithm="RS256"):
-        super().__init__(algorithm)
-        self._fetcher = JwksFetcher(jwks_url)
-
-    def _fetch_key(self, key_id=None):
-        return self._fetcher.get_key(key_id)
-
-
 class JwksFetcher:
     """Class that fetches and holds a JSON web key set.
     This class makes use of an in-memory cache. For it to work properly, define this instance once and re-use it.
@@ -239,6 +223,23 @@ class JwksFetcher:
         raise TokenValidationError(f'RSA Public Key with ID "{key_id}" was not found.')
 
 
+class AsymmetricSignatureVerifier(SignatureVerifier):
+    """Verifier for RSA signatures, which rely on public key certificates.
+
+    Args:
+        jwks_url (str): The url where the JWK set is located.
+        algorithm (str, optional): The expected signing algorithm. Defaults to "RS256".
+        cache_ttl (int, optional): The lifetime of the JWK set cache in seconds. Defaults to 600 seconds.
+    """
+
+    def __init__(self, jwks_url, algorithm="RS256", cache_ttl=JwksFetcher.CACHE_TTL):
+        super().__init__(algorithm)
+        self._fetcher = JwksFetcher(jwks_url, cache_ttl)
+
+    def _fetch_key(self, key_id=None):
+        return self._fetcher.get_key(key_id)
+
+
 class TokenVerifier:
     """Class that verifies ID tokens following the steps defined in the OpenID Connect spec.
     An OpenID Connect ID token is not meant to be consumed until it's verified.


Test patch:
diff --git a/auth0/test/authentication/test_token_verifier.py b/auth0/test/authentication/test_token_verifier.py
index beda2e6..df2af2e 100644
--- a/auth0/test/authentication/test_token_verifier.py
+++ b/auth0/test/authentication/test_token_verifier.py
@@ -69,6 +69,14 @@ class TestSignatureVerifier(unittest.TestCase):
         verifier = AsymmetricSignatureVerifier("some URL")
         self.assertEqual(verifier._algorithm, "RS256")
 
+    def test_asymmetric_verifier_uses_default_jwks_cache_ttl(self):
+        verifier = AsymmetricSignatureVerifier("some URL")
+        self.assertEqual(verifier._fetcher._cache_ttl, JwksFetcher.CACHE_TTL)
+
+    def test_asymmetric_verifier_uses_provided_jwks_cache_ttl(self):
+        verifier = AsymmetricSignatureVerifier("some URL", cache_ttl=3600)
+        self.assertEqual(verifier._fetcher._cache_ttl, 3600)
+
     def test_symmetric_verifier_fetches_key(self):
         verifier = SymmetricSignatureVerifier("some secret")
         key = verifier._fetch_key()