Skip to content

Commit a2601ad

Browse files
committed
Merge pull request jpadilla#126 from mark-adams/iat-nbf-checks
Add check for iat and new exception for nbf claims
2 parents 54ed26b + 49db1ad commit a2601ad

File tree

4 files changed

+37
-19
lines changed

4 files changed

+37
-19
lines changed

jwt/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
encode, decode, register_algorithm, unregister_algorithm, PyJWT
2121
)
2222
from .exceptions import (
23-
InvalidTokenError, DecodeError, ExpiredSignatureError,
24-
InvalidAudienceError, InvalidIssuerError,
25-
ExpiredSignature, InvalidAudience, InvalidIssuer
23+
InvalidTokenError, DecodeError, InvalidAudienceError,
24+
ExpiredSignatureError, ImmatureSignatureError, InvalidIssuedAtError,
25+
InvalidIssuerError, ExpiredSignature, InvalidAudience, InvalidIssuer
2626
)

jwt/api.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
from .algorithms import Algorithm, get_default_algorithms # NOQA
99
from .compat import string_types, text_type, timedelta_total_seconds
1010
from .exceptions import (
11-
DecodeError, ExpiredSignatureError, InvalidAlgorithmError,
12-
InvalidAudienceError, InvalidIssuerError
11+
DecodeError, ExpiredSignatureError, ImmatureSignatureError,
12+
InvalidAlgorithmError, InvalidAudienceError, InvalidIssuedAtError,
13+
InvalidIssuerError
1314
)
1415
from .utils import base64url_decode, base64url_encode
1516

@@ -184,32 +185,33 @@ def _validate_claims(self, payload, verify_expiration=True, leeway=0,
184185
if not isinstance(audience, (string_types, type(None))):
185186
raise TypeError('audience must be a string or None')
186187

188+
now = timegm(datetime.utcnow().utctimetuple())
189+
187190
if 'iat' in payload:
188191
try:
189-
int(payload['iat'])
192+
iat = int(payload['iat'])
190193
except ValueError:
191194
raise DecodeError('Issued At claim (iat) must be an integer.')
192195

196+
if iat > (now + leeway):
197+
raise InvalidIssuedAtError('Issued At claim (iat) cannot be in the future.')
198+
193199
if 'nbf' in payload and verify_expiration:
194200
try:
195201
nbf = int(payload['nbf'])
196202
except ValueError:
197203
raise DecodeError('Not Before claim (nbf) must be an integer.')
198204

199-
utc_timestamp = timegm(datetime.utcnow().utctimetuple())
200-
201-
if nbf > (utc_timestamp + leeway):
202-
raise ExpiredSignatureError('Signature not yet valid')
205+
if nbf > (now + leeway):
206+
raise ImmatureSignatureError('The token is not yet valid (nbf)')
203207

204208
if 'exp' in payload and verify_expiration:
205209
try:
206210
exp = int(payload['exp'])
207211
except ValueError:
208212
raise DecodeError('Expiration Time claim (exp) must be an integer.')
209213

210-
utc_timestamp = timegm(datetime.utcnow().utctimetuple())
211-
212-
if exp < (utc_timestamp - leeway):
214+
if exp < (now - leeway):
213215
raise ExpiredSignatureError('Signature has expired')
214216

215217
if 'aud' in payload:

jwt/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ class InvalidIssuerError(InvalidTokenError):
1818
pass
1919

2020

21+
class InvalidIssuedAtError(InvalidTokenError):
22+
pass
23+
24+
25+
class ImmatureSignatureError(InvalidTokenError):
26+
pass
27+
28+
2129
class InvalidKeyError(Exception):
2230
pass
2331

tests/test_api.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
from jwt.algorithms import Algorithm
1010
from jwt.api import PyJWT
1111
from jwt.exceptions import (
12-
DecodeError, ExpiredSignatureError, InvalidAlgorithmError,
13-
InvalidAudienceError, InvalidIssuerError
12+
DecodeError, ExpiredSignatureError, ImmatureSignatureError,
13+
InvalidAlgorithmError, InvalidAudienceError, InvalidIssuedAtError,
14+
InvalidIssuerError
1415
)
1516

1617
from .compat import text_type, unittest
@@ -210,7 +211,7 @@ def test_decode_raises_exception_if_iat_is_not_int(self):
210211
'eyJpYXQiOiJub3QtYW4taW50In0.'
211212
'H1GmcQgSySa5LOKYbzGm--b1OmRbHFkyk8pq811FzZM')
212213

213-
with self.assertRaisesRegexp(DecodeError, 'iat'):
214+
with self.assertRaises(DecodeError):
214215
self.jwt.decode(example_jwt, 'secret')
215216

216217
def test_decode_raises_exception_if_nbf_is_not_int(self):
@@ -219,9 +220,16 @@ def test_decode_raises_exception_if_nbf_is_not_int(self):
219220
'eyJuYmYiOiJub3QtYW4taW50In0.'
220221
'c25hldC8G2ZamC8uKpax9sYMTgdZo3cxrmzFHaAAluw')
221222

222-
with self.assertRaisesRegexp(DecodeError, 'nbf'):
223+
with self.assertRaises(DecodeError):
223224
self.jwt.decode(example_jwt, 'secret')
224225

226+
def test_decode_raises_exception_if_iat_in_the_future(self):
227+
now = datetime.utcnow()
228+
token = self.jwt.encode({'iat': now + timedelta(days=1)}, key='secret')
229+
230+
with self.assertRaises(InvalidIssuedAtError):
231+
self.jwt.decode(token, 'secret')
232+
225233
def test_encode_datetime(self):
226234
secret = 'secret'
227235
current_datetime = datetime.utcnow()
@@ -451,7 +459,7 @@ def test_decode_with_notbefore(self):
451459
secret = 'secret'
452460
jwt_message = self.jwt.encode(self.payload, secret)
453461

454-
with self.assertRaises(ExpiredSignatureError):
462+
with self.assertRaises(ImmatureSignatureError):
455463
self.jwt.decode(jwt_message, secret)
456464

457465
def test_decode_skip_expiration_verification(self):
@@ -492,7 +500,7 @@ def test_decode_with_notbefore_with_leeway(self):
492500
# With 13 seconds leeway, should be ok
493501
self.jwt.decode(jwt_message, secret, leeway=13)
494502

495-
with self.assertRaises(ExpiredSignatureError):
503+
with self.assertRaises(ImmatureSignatureError):
496504
self.jwt.decode(jwt_message, secret, leeway=1)
497505

498506
def test_decode_with_algo_none_should_fail(self):

0 commit comments

Comments
 (0)