diff --git a/CHANGELOG.md b/CHANGELOG.md index e4aac0f19..89fc629a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ ## Unreleased -## Version 4.7.3 +## Version 4.8.0 * Add integration instructions for drf-yasg ([#145](https://github.com/jazzband/djangorestframework-simplejwt/pull/145)) * Verify Serializer Should Honour Blacklist ([#239](https://github.com/jazzband/djangorestframework-simplejwt/pull/239)) * Added missing import in getting_started docs ([#431](https://github.com/jazzband/djangorestframework-simplejwt/pull/431)) * Use import_string for token_backend ([#435](https://github.com/jazzband/djangorestframework-simplejwt/pull/435)) +* Add JWKS support ([#437](https://github.com/jazzband/djangorestframework-simplejwt/pull/435)) +* Use pathlib instead of open in setup.py ([#339](https://github.com/jazzband/djangorestframework-simplejwt/pull/339)) +* Optimize default_user_authentication_rule ([#441](https://github.com/jazzband/djangorestframework-simplejwt/pull/441)) +* Add Leeway option to decode ([#445](https://github.com/jazzband/djangorestframework-simplejwt/pull/445)) ## Version 4.7.2 diff --git a/docs/settings.rst b/docs/settings.rst index 5641a02a4..ea407f0c6 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -27,6 +27,7 @@ Some of Simple JWT's behavior can be customized through settings variables in 'AUDIENCE': None, 'ISSUER': None, 'JWK_URL': None, + 'LEEWAY': 0, 'AUTH_HEADER_TYPES': ('Bearer',), 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', @@ -156,6 +157,15 @@ signing of tokens. When using Auth0 for example you might set this to this field is excluded from the token backend and is not used during validation. +``LEEWAY`` +---------- + +Leeway is used to give some margin to the expiration time. This can be an +integer for seconds or a ``datetime.timedelta``. Please reference +https://pyjwt.readthedocs.io/en/latest/usage.html#expiration-time-claim-exp +for more information. + + ``AUTH_HEADER_TYPES`` --------------------- diff --git a/rest_framework_simplejwt/backends.py b/rest_framework_simplejwt/backends.py index b3048b2f8..d4844471f 100644 --- a/rest_framework_simplejwt/backends.py +++ b/rest_framework_simplejwt/backends.py @@ -24,6 +24,7 @@ def __init__( audience=None, issuer=None, jwk_url: str = None, + leeway=0, ): self._validate_algorithm(algorithm) @@ -33,6 +34,7 @@ def __init__( self.issuer = issuer self.jwks_client = PyJWKClient(jwk_url) if jwk_url else None + self.leeway = leeway if algorithm.startswith("HS"): self.verifying_key = signing_key @@ -92,6 +94,7 @@ def decode(self, token, verify=True): verify=verify, audience=self.audience, issuer=self.issuer, + leeway=self.leeway, options={ 'verify_aud': self.audience is not None, 'verify_signature': verify, diff --git a/rest_framework_simplejwt/settings.py b/rest_framework_simplejwt/settings.py index 39459b44f..9cd790599 100644 --- a/rest_framework_simplejwt/settings.py +++ b/rest_framework_simplejwt/settings.py @@ -22,6 +22,7 @@ 'AUDIENCE': None, 'ISSUER': None, 'JWK_URL': None, + 'LEEWAY': 0, 'AUTH_HEADER_TYPES': ('Bearer',), 'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', diff --git a/rest_framework_simplejwt/state.py b/rest_framework_simplejwt/state.py index a901adf15..4fe94faef 100644 --- a/rest_framework_simplejwt/state.py +++ b/rest_framework_simplejwt/state.py @@ -8,4 +8,5 @@ api_settings.AUDIENCE, api_settings.ISSUER, api_settings.JWK_URL, + api_settings.LEEWAY, ) diff --git a/tests/test_backends.py b/tests/test_backends.py index bca2780e8..3d204718a 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -21,10 +21,12 @@ JWK_URL = 'https://randomstring.auth0.com/.well-known/jwks.json' +LEEWAY = 100 class TestTokenBackend(TestCase): def setUp(self): self.hmac_token_backend = TokenBackend('HS256', SECRET) + self.hmac_leeway_token_backend = TokenBackend('HS256', SECRET, leeway=LEEWAY) self.rsa_token_backend = TokenBackend('RS256', PRIVATE_KEY, PUBLIC_KEY) self.aud_iss_token_backend = TokenBackend('RS256', PRIVATE_KEY, PUBLIC_KEY, AUDIENCE, ISSUER) self.payload = {'foo': 'bar'} @@ -283,3 +285,21 @@ def test_decode_when_token_algorithm_does_not_match(self): with self.assertRaisesRegex(TokenBackendError, 'Invalid algorithm specified'): self.hmac_token_backend.decode(token) + + def test_decode_leeway_hmac_fail(self): + self.payload["exp"] = datetime_to_epoch(aware_utcnow() - timedelta(seconds=LEEWAY * 2)) + + expired_token = jwt.encode(self.payload, SECRET, algorithm='HS256') + + with self.assertRaises(TokenBackendError): + self.hmac_leeway_token_backend.decode(expired_token) + + def test_decode_leeway_hmac_success(self): + self.payload["exp"] = datetime_to_epoch(aware_utcnow() - timedelta(seconds=LEEWAY / 2)) + + expired_token = jwt.encode(self.payload, SECRET, algorithm='HS256') + + self.assertEqual( + self.hmac_leeway_token_backend.decode(expired_token), + self.payload, + )