Skip to content

Commit f602132

Browse files
authored
Add specific "token expired" exceptions (#830)
* Add a specific backend exception for expired tokens To later allow specific handling for this case in the layers above. * Add a separate TokenError subclass for expired tokens To allow the caller to handle expired tokens separately from invalid ones without resorting to string matching.
1 parent 9a66629 commit f602132

6 files changed

Lines changed: 45 additions & 18 deletions

File tree

rest_framework_simplejwt/backends.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@
55

66
import jwt
77
from django.utils.translation import gettext_lazy as _
8-
from jwt import InvalidAlgorithmError, InvalidTokenError, algorithms
9-
10-
from .exceptions import TokenBackendError
8+
from jwt import (
9+
ExpiredSignatureError,
10+
InvalidAlgorithmError,
11+
InvalidTokenError,
12+
algorithms,
13+
)
14+
15+
from .exceptions import TokenBackendError, TokenBackendExpiredToken
1116
from .tokens import Token
1217
from .utils import format_lazy
1318

@@ -101,7 +106,7 @@ def get_verifying_key(self, token: Token) -> Optional[str]:
101106
try:
102107
return self.jwks_client.get_signing_key_from_jwt(token).key
103108
except PyJWKClientError as ex:
104-
raise TokenBackendError(_("Token is invalid or expired")) from ex
109+
raise TokenBackendError(_("Token is invalid")) from ex
105110

106111
return self.verifying_key
107112

@@ -150,5 +155,7 @@ def decode(self, token: Token, verify: bool = True) -> Dict[str, Any]:
150155
)
151156
except InvalidAlgorithmError as ex:
152157
raise TokenBackendError(_("Invalid algorithm specified")) from ex
158+
except ExpiredSignatureError as ex:
159+
raise TokenBackendExpiredToken(_("Token is expired")) from ex
153160
except InvalidTokenError as ex:
154-
raise TokenBackendError(_("Token is invalid or expired")) from ex
161+
raise TokenBackendError(_("Token is invalid")) from ex

rest_framework_simplejwt/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,18 @@ class TokenError(Exception):
88
pass
99

1010

11+
class ExpiredTokenError(TokenError):
12+
pass
13+
14+
1115
class TokenBackendError(Exception):
1216
pass
1317

1418

19+
class TokenBackendExpiredToken(TokenBackendError):
20+
pass
21+
22+
1523
class DetailDictMixin:
1624
default_detail: str
1725
default_code: str

rest_framework_simplejwt/tokens.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
from django.utils.module_loading import import_string
88
from django.utils.translation import gettext_lazy as _
99

10-
from .exceptions import TokenBackendError, TokenError
10+
from .exceptions import (
11+
ExpiredTokenError,
12+
TokenBackendError,
13+
TokenBackendExpiredToken,
14+
TokenError,
15+
)
1116
from .models import TokenUser
1217
from .settings import api_settings
1318
from .token_blacklist.models import BlacklistedToken, OutstandingToken
@@ -56,8 +61,10 @@ def __init__(self, token: Optional["Token"] = None, verify: bool = True) -> None
5661
# Decode token
5762
try:
5863
self.payload = token_backend.decode(token, verify=verify)
64+
except TokenBackendExpiredToken:
65+
raise ExpiredTokenError(_("Token is expired"))
5966
except TokenBackendError:
60-
raise TokenError(_("Token is invalid or expired"))
67+
raise TokenError(_("Token is invalid"))
6168

6269
if verify:
6370
self.verify()

tests/test_backends.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
from jwt import algorithms
1515

1616
from rest_framework_simplejwt.backends import JWK_CLIENT_AVAILABLE, TokenBackend
17-
from rest_framework_simplejwt.exceptions import TokenBackendError
17+
from rest_framework_simplejwt.exceptions import (
18+
TokenBackendError,
19+
TokenBackendExpiredToken,
20+
)
1821
from rest_framework_simplejwt.utils import aware_utcnow, datetime_to_epoch, make_utc
1922
from tests.keys import (
2023
ES256_PRIVATE_KEY,
@@ -191,7 +194,7 @@ def test_decode_with_expiry(self):
191194
self.payload, backend.signing_key, algorithm=backend.algorithm
192195
)
193196

194-
with self.assertRaises(TokenBackendError):
197+
with self.assertRaises(TokenBackendExpiredToken):
195198
backend.decode(expired_token)
196199

197200
def test_decode_with_invalid_sig(self):
@@ -346,9 +349,7 @@ def test_decode_jwk_missing_key_raises_tokenbackenderror(self):
346349
"RS256", PRIVATE_KEY, PUBLIC_KEY, AUDIENCE, ISSUER, JWK_URL
347350
)
348351

349-
with self.assertRaisesRegex(
350-
TokenBackendError, "Token is invalid or expired"
351-
):
352+
with self.assertRaisesRegex(TokenBackendError, "Token is invalid"):
352353
jwk_token_backend.decode(token)
353354

354355
def test_decode_when_algorithm_not_available(self):

tests/test_serializers.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def test_it_should_not_validate_if_token_invalid(self):
206206
with self.assertRaises(TokenError) as e:
207207
s.is_valid()
208208

209-
self.assertIn("invalid or expired", e.exception.args[0])
209+
self.assertIn("expired", e.exception.args[0])
210210

211211
def test_it_should_raise_token_error_if_token_has_no_refresh_exp_claim(self):
212212
token = SlidingToken()
@@ -337,7 +337,7 @@ def test_it_should_raise_token_error_if_token_invalid(self):
337337
with self.assertRaises(TokenError) as e:
338338
s.is_valid()
339339

340-
self.assertIn("invalid or expired", e.exception.args[0])
340+
self.assertIn("expired", e.exception.args[0])
341341

342342
def test_it_should_raise_token_error_if_token_has_wrong_type(self):
343343
token = RefreshToken()
@@ -503,7 +503,7 @@ def test_it_should_raise_token_error_if_token_invalid(self):
503503
with self.assertRaises(TokenError) as e:
504504
s.is_valid()
505505

506-
self.assertIn("invalid or expired", e.exception.args[0])
506+
self.assertIn("expired", e.exception.args[0])
507507

508508
def test_it_should_not_raise_token_error_if_token_has_wrong_type(self):
509509
token = RefreshToken()
@@ -548,7 +548,7 @@ def test_it_should_raise_token_error_if_token_invalid(self):
548548
with self.assertRaises(TokenError) as e:
549549
s.is_valid()
550550

551-
self.assertIn("invalid or expired", e.exception.args[0])
551+
self.assertIn("expired", e.exception.args[0])
552552

553553
def test_it_should_raise_token_error_if_token_has_wrong_type(self):
554554
token = RefreshToken()

tests/test_tokens.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
from django.test import TestCase
77
from jose import jwt
88

9-
from rest_framework_simplejwt.exceptions import TokenBackendError, TokenError
9+
from rest_framework_simplejwt.exceptions import (
10+
ExpiredTokenError,
11+
TokenBackendError,
12+
TokenError,
13+
)
1014
from rest_framework_simplejwt.settings import api_settings
1115
from rest_framework_simplejwt.state import token_backend
1216
from rest_framework_simplejwt.tokens import (
@@ -157,7 +161,7 @@ def test_init_expired_token_given(self):
157161
t = MyToken()
158162
t.set_exp(lifetime=-timedelta(seconds=1))
159163

160-
with self.assertRaises(TokenError):
164+
with self.assertRaises(ExpiredTokenError):
161165
MyToken(str(t))
162166

163167
def test_init_no_type_token_given(self):

0 commit comments

Comments
 (0)