Skip to content

Commit 83d07fc

Browse files
committed
Added checks on iat to make sure that a token can't be issued for the
future Changed nbf exception to ImmatureSignatureError
1 parent 54ed26b commit 83d07fc

File tree

4 files changed

+36
-18
lines changed

4 files changed

+36
-18
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: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
from .compat import string_types, text_type, timedelta_total_seconds
1010
from .exceptions import (
1111
DecodeError, ExpiredSignatureError, InvalidAlgorithmError,
12-
InvalidAudienceError, InvalidIssuerError
12+
InvalidAudienceError, InvalidIssuerError,
13+
InvalidIssuedAtError, ImmatureSignatureError
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: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,18 @@ class InvalidIssuerError(InvalidTokenError):
1818
pass
1919

2020

21-
class InvalidKeyError(Exception):
21+
class InvalidIssuedAtError(InvalidTokenError):
22+
pass
23+
24+
25+
class ImmatureSignatureError(InvalidTokenError):
2226
pass
2327

2428

29+
class InvalidKeyError(Exception):
30+
pass
31+
32+
2533
class InvalidAlgorithmError(InvalidTokenError):
2634
pass
2735

tests/test_api.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from jwt.api import PyJWT
1111
from jwt.exceptions import (
1212
DecodeError, ExpiredSignatureError, InvalidAlgorithmError,
13-
InvalidAudienceError, InvalidIssuerError
13+
InvalidAudienceError, InvalidIssuerError, InvalidIssuedAtError,
14+
ImmatureSignatureError
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)