Skip to content

Commit 086cdf5

Browse files
committed
Merge pull request jpadilla#60 from wbolster/issue-59
Add base exception for invalid tokens
2 parents 37edf5a + 0f6957a commit 086cdf5

File tree

3 files changed

+64
-26
lines changed

3 files changed

+64
-26
lines changed

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ You can still get the payload by setting the `verify` argument to `False`.
4343
jwt.decode('someJWTstring', verify=False)
4444
```
4545

46+
The `decode()` function can raise other exceptions, e.g. for invalid issuer or audience (see below). All exceptions that signify that the token is invalid extend from the base `InvalidTokenError` exception class, so applications can use this approach to catch any issues relating to invalid tokens:
47+
48+
```python
49+
try:
50+
payload = jwt.decode('someJWTstring')
51+
except jwt.InvalidTokenError:
52+
pass # do something sensible here, e.g. return HTTP 403 status code
53+
```
54+
55+
4656
## Algorithms
4757

4858
The JWT spec supports several algorithms for cryptographic signing. This library
@@ -112,14 +122,14 @@ jwt.encode({'exp': datetime.utcnow()}, 'secret')
112122
```
113123

114124
Expiration time is automatically verified in `jwt.decode()` and raises
115-
`jwt.ExpiredSignature` if the expiration time is in the past:
125+
`jwt.ExpiredSignatureError` if the expiration time is in the past:
116126

117127
```python
118128
import jwt
119129

120130
try:
121131
jwt.decode('JWT_STRING', 'secret')
122-
except jwt.ExpiredSignature:
132+
except jwt.ExpiredSignatureError:
123133
# Signature has expired
124134
```
125135

@@ -187,6 +197,9 @@ token = jwt.encode(payload, 'secret')
187197
decoded = jwt.decode(token, 'secret', issuer='urn:foo')
188198
```
189199

200+
If the issuer claim is incorrect, `jwt.InvalidIssuerError` will be raised.
201+
202+
190203
### Audience Claim
191204

192205
> The aud (audience) claim identifies the recipients that the JWT is intended for. Each principal intended to process the JWT MUST identify itself with a value in the audience claim. If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected. In the general case, the aud value is an array of case-sensitive strings, each containing a StringOrURI value. In the special case when the JWT has one audience, the aud value MAY be a single case-sensitive string containing a StringOrURI value. The interpretation of audience values is generally application specific. Use of this claim is OPTIONAL.
@@ -204,6 +217,9 @@ token = jwt.encode(payload, 'secret')
204217
decoded = jwt.decode(token, 'secret', audience='urn:foo')
205218
```
206219

220+
If the audience claim is incorrect, `jwt.InvalidAudienceError` will be raised.
221+
222+
207223
## License
208224

209225
MIT

jwt/__init__.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,49 @@
2828

2929
__version__ = '0.4.0'
3030
__all__ = [
31-
'encode', 'decode', 'DecodeError', 'ExpiredSignature',
32-
'InvalidAudience', 'InvalidIssuer'
31+
# Functions
32+
'encode',
33+
'decode',
34+
35+
# Exceptions
36+
'InvalidTokenError',
37+
'DecodeError',
38+
'ExpiredSignatureError',
39+
'InvalidAudienceError',
40+
'InvalidIssuerError'
41+
42+
# Deprecated aliases
43+
'ExpiredSignature',
44+
'InvalidAudience',
45+
'InvalidIssuer',
3346
]
3447

3548

36-
class DecodeError(Exception):
49+
class InvalidTokenError(Exception):
3750
pass
3851

3952

40-
class ExpiredSignature(Exception):
53+
class DecodeError(InvalidTokenError):
4154
pass
4255

4356

44-
class InvalidAudience(Exception):
57+
class ExpiredSignatureError(InvalidTokenError):
4558
pass
4659

4760

48-
class InvalidIssuer(Exception):
61+
class InvalidAudienceError(InvalidTokenError):
4962
pass
5063

5164

65+
class InvalidIssuerError(InvalidTokenError):
66+
pass
67+
68+
69+
# Compatibility aliases (deprecated)
70+
ExpiredSignature = ExpiredSignatureError
71+
InvalidAudience = InvalidAudienceError
72+
InvalidIssuer = InvalidIssuerError
73+
5274
signing_methods = {
5375
'none': lambda msg, key: b'',
5476
'HS256': lambda msg, key: hmac.new(key, msg, hashlib.sha256).digest(),
@@ -395,13 +417,13 @@ def verify_signature(payload, signing_input, header, signature, key='',
395417
utc_timestamp = timegm(datetime.utcnow().utctimetuple())
396418

397419
if payload['nbf'] > (utc_timestamp + leeway):
398-
raise ExpiredSignature('Signature not yet valid')
420+
raise ExpiredSignatureError('Signature not yet valid')
399421

400422
if 'exp' in payload and verify_expiration:
401423
utc_timestamp = timegm(datetime.utcnow().utctimetuple())
402424

403425
if payload['exp'] < (utc_timestamp - leeway):
404-
raise ExpiredSignature('Signature has expired')
426+
raise ExpiredSignatureError('Signature has expired')
405427

406428
if audience is not None:
407429
if isinstance(audience, list):
@@ -410,8 +432,8 @@ def verify_signature(payload, signing_input, header, signature, key='',
410432
audiences = [audience]
411433

412434
if payload.get('aud') not in audiences:
413-
raise InvalidAudience('Invalid audience')
435+
raise InvalidAudienceError('Invalid audience')
414436

415437
if issuer is not None:
416438
if payload.get('iss') != issuer:
417-
raise InvalidIssuer('Invalid issuer')
439+
raise InvalidIssuerError('Invalid issuer')

tests/test_jwt.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -357,13 +357,13 @@ def test_decode_with_expiration(self):
357357
jwt_message = jwt.encode(self.payload, secret)
358358

359359
self.assertRaises(
360-
jwt.ExpiredSignature,
360+
jwt.ExpiredSignatureError,
361361
lambda: jwt.decode(jwt_message, secret))
362362

363363
decoded_payload, signing, header, signature = jwt.load(jwt_message)
364364

365365
self.assertRaises(
366-
jwt.ExpiredSignature,
366+
jwt.ExpiredSignatureError,
367367
lambda: jwt.verify_signature(
368368
decoded_payload, signing, header, signature, secret))
369369

@@ -373,13 +373,13 @@ def test_decode_with_notbefore(self):
373373
jwt_message = jwt.encode(self.payload, secret)
374374

375375
self.assertRaises(
376-
jwt.ExpiredSignature,
376+
jwt.ExpiredSignatureError,
377377
lambda: jwt.decode(jwt_message, secret))
378378

379379
decoded_payload, signing, header, signature = jwt.load(jwt_message)
380380

381381
self.assertRaises(
382-
jwt.ExpiredSignature,
382+
jwt.ExpiredSignatureError,
383383
lambda: jwt.verify_signature(
384384
decoded_payload, signing, header, signature, secret))
385385

@@ -422,11 +422,11 @@ def test_decode_with_expiration_with_leeway(self):
422422
# With 1 seconds, should fail
423423
for leeway in (1, timedelta(seconds=1)):
424424
self.assertRaises(
425-
jwt.ExpiredSignature,
425+
jwt.ExpiredSignatureError,
426426
lambda: jwt.decode(jwt_message, secret, leeway=leeway))
427427

428428
self.assertRaises(
429-
jwt.ExpiredSignature,
429+
jwt.ExpiredSignatureError,
430430
lambda: jwt.verify_signature(decoded_payload, signing,
431431
header, signature, secret,
432432
leeway=leeway))
@@ -446,11 +446,11 @@ def test_decode_with_notbefore_with_leeway(self):
446446

447447
# With 1 seconds, should fail
448448
self.assertRaises(
449-
jwt.ExpiredSignature,
449+
jwt.ExpiredSignatureError,
450450
lambda: jwt.decode(jwt_message, secret, leeway=1))
451451

452452
self.assertRaises(
453-
jwt.ExpiredSignature,
453+
jwt.ExpiredSignatureError,
454454
lambda: jwt.verify_signature(decoded_payload, signing,
455455
header, signature, secret, leeway=1))
456456

@@ -737,7 +737,7 @@ def test_raise_exception_invalid_audience(self):
737737
token = jwt.encode(payload, 'secret')
738738

739739
self.assertRaises(
740-
jwt.InvalidAudience,
740+
jwt.InvalidAudienceError,
741741
lambda: jwt.decode(token, 'secret', audience=audience))
742742

743743
def test_raise_exception_invalid_audience_in_array(self):
@@ -751,7 +751,7 @@ def test_raise_exception_invalid_audience_in_array(self):
751751
token = jwt.encode(payload, 'secret')
752752

753753
self.assertRaises(
754-
jwt.InvalidAudience,
754+
jwt.InvalidAudienceError,
755755
lambda: jwt.decode(token, 'secret', audience=audience))
756756

757757
def test_raise_exception_token_without_audience(self):
@@ -764,7 +764,7 @@ def test_raise_exception_token_without_audience(self):
764764
token = jwt.encode(payload, 'secret')
765765

766766
self.assertRaises(
767-
jwt.InvalidAudience,
767+
jwt.InvalidAudienceError,
768768
lambda: jwt.decode(token, 'secret', audience=audience))
769769

770770
def test_raise_exception_token_without_audience_in_array(self):
@@ -777,7 +777,7 @@ def test_raise_exception_token_without_audience_in_array(self):
777777
token = jwt.encode(payload, 'secret')
778778

779779
self.assertRaises(
780-
jwt.InvalidAudience,
780+
jwt.InvalidAudienceError,
781781
lambda: jwt.decode(token, 'secret', audience=audience))
782782

783783
def test_check_issuer(self):
@@ -804,7 +804,7 @@ def test_raise_exception_invalid_issuer(self):
804804
token = jwt.encode(payload, 'secret')
805805

806806
self.assertRaises(
807-
jwt.InvalidIssuer,
807+
jwt.InvalidIssuerError,
808808
lambda: jwt.decode(token, 'secret', issuer=issuer))
809809

810810
def test_raise_exception_token_without_issuer(self):
@@ -817,7 +817,7 @@ def test_raise_exception_token_without_issuer(self):
817817
token = jwt.encode(payload, 'secret')
818818

819819
self.assertRaises(
820-
jwt.InvalidIssuer,
820+
jwt.InvalidIssuerError,
821821
lambda: jwt.decode(token, 'secret', issuer=issuer))
822822

823823
def test_custom_json_encoder(self):

0 commit comments

Comments
 (0)