pyjwt: Why validate that 'iat' is not in the future?
In https://github.com/jpadilla/pyjwt/blob/c5ee34e86bc42bef60ef6e701df569c2c86a5d5d/jwt/api_jwt.py#L129:
if iat > (now + leeway):
raise InvalidIssuedAtError('Issued At claim (iat) cannot be in'
' the future.')
I just debugged an issue in prod where jwt.decode()
failed because of this. Mostly because the other party’s jwt lib added ‘iat’ a few seconds or minutes ahead of our clock time (‘clock skew’ as mentioned in JWT specs).
I can’t find any place in the specs that says that a JWT should be invalid if ‘iat’ is in the future. It seems like it’s just there to be informative. I can use ‘nbf’ if I want to specify a “time before which the token MUST NOT be accepted for processing”
I consulted
So either
- I’m wrong and there is a JWT spec that says this is important to check. I want to know this, because if it’s out there, I shouldn’t just catch these errors from PyJWT and
pass
. Regardless of whether @jpadilla wants to remove thatraise
in his lib. - PyJWT is checking that unnecessarily, and we should remove it to be more compliant
About this issue
- Original URL
- State: closed
- Created 9 years ago
- Reactions: 1
- Comments: 16 (4 by maintainers)
Commits related to this issue
- Stop rejecting tokens with future 'iat' values RFC 7519 does not specify or even suggest this type of validation on the 'iat' claim and it has caused issues for several consumers of PyJWT. This chan... — committed to jpadilla/pyjwt by mark-adams 7 years ago
- Stop rejecting tokens with future 'iat' values RFC 7519 does not specify or even suggest this type of validation on the 'iat' claim and it has caused issues for several consumers of PyJWT. This chan... — committed to jpadilla/pyjwt by mark-adams 7 years ago
- Stop rejecting tokens with future 'iat' values RFC 7519 does not specify or even suggest this type of validation on the 'iat' claim and it has caused issues for several consumers of PyJWT. This chan... — committed to jpadilla/pyjwt by mark-adams 7 years ago
- Don't test that JWT's issued in future are invalid Remove two tests that JWT's whose `iat` value claims that they were issued in the future fail validation. These two tests fail on newer versions of... — committed to hypothesis/h by seanh 7 years ago
- Don't test that JWT's issued in future are invalid Remove two tests that JWT's whose `iat` value claims that they were issued in the future fail validation. These two tests fail on newer versions of... — committed to hypothesis/h by seanh 7 years ago
RFC 7519 says:
In all circumstances except clock skew, it doesn’t seem like it makes any sense for a JWT to be valid if, by its own claim, it hasn’t been created yet. That’s why we included this validation. JWT doesn’t say you have to have this validation to be compliant, but it seems logical and enforces integrity in the data.
I’m against removing this validation, especially when you can already easily disable it by using
jwt.decode(token, secret, options={'verify_iat': False})
or account for the clock skew by using the leeway parameter:jwt.decode(token, secret, leeway=10)
Hi, here’s a real word case of this issue causing problems.
Our infrastructure involves generating tokens on a server in Amazon’s cloud and checking them on another one in Google’s cloud using
jwt.decode()
.At midnight between 2016 December 31 and 2017 January 1, a leap second was introduced into UTC. Both clouds ‘smeared’ this second linearly over a period of time centered around that midnight: https://aws.amazon.com/blogs/aws/look-before-you-leap-december-31-2016-leap-second-on-aws/ https://developers.google.com/time/smear
However, Amazon used 24 hour period and Google used 20 hour. This led to Amazon’s clock being ahead of Google’s on December 31 between 12:00:00 and 23:59:59, and our service was unavailable during most of that period due to
jwt.decode()
failing.So is the recommended fix to just add the
options
kwarg on all decode calls? Is there some package config I can tweak or something I can monkeypatch to disable it globally?Still seems annoying to be bitten by enforcing something not mentioned in the RFC. All it says is that (emph. added)
A negative age is startling I suppose, but nothing else seems to say that’s forbidden.
Thanks petevg! Glad you enjoy it! 😃
A couple of things in response:
How can it be unreasonable to assume two computers have a similar definition of when “now” is? We make these sort of assumptions all the time in all sorts of systems (public-key certificates used for HTTPS, EXP claims on JWT, TOTP-based two-factor authentication, etc.). I think it is completely reasonable, in particular when working with security, to assume two computers have a similar understanding of when “now” is. I do believe it IS unreasonable however to assume they have the EXACT same definition of now. That’s why we include the ability to add the leeway parameter as almost everything on the Internet that relies on time does to compensate for the fact that exact time synchronization is hard.
Setting verify_iat to default to
False
is an option but we’d probably want to bump the version number and make it clear if we make that sort of change (because some people may be relying on that behavior). In the mean time, settingoptions={'verify_iat': False}
seems like its still a reasonable workaround.If there’s a way we can make this behavior clearer in the documentation, I’m 100% sure we’d be glad to do that. Any ideas?
Thanks again for participating in the discussion 👍
Hi there. Since the iat check introduces a situation where the a default install of PyJWT will break in typical production circumstances (or, if you’re lucky, in testing, which is what happened to me), I’d like to second the request to set verify_iat to False by default. I don’t think that it’s reasonable to expect any two servers to have a shared definition of “now”, or to make strict checks based on time between two machines.
Regardless, thank you for all the work putting this library together – it has been really helpful and useful. 😃
Thanks for the PR! Just a reminder, instead of having to fork and package your own version of PyJWT, which seems a bit like overkill, you can totally bypass the check (exactly what your PR does), if you simply pass
options={'verify_iat': False}
to your jwt.decode() call with the existing code.I don’t like the idea of removing this check completely. Plenty of libraries have reasonable functionality that is not necessarily part of any particular spec. The strongest argument would be that we should set
verify_iat
toFalse
by default and leave this as a helpful feature that developers can turn on if they want the additional functionality. That’s a PR I could get behind!Update for anybody finding this discussion later:
iat <= now
validation was removed in 1.5.0 via #252iat <= now
validation was re-added in 2.6.0 via #794 (current state)Two other related issues:
iat <= now
validation)The issue is more that unless you explicitly disable the check every time or add in leeway for clock skew, this can cause random errors. The RFC talks about minutes of skew, which seems crazy, but even milliseconds of skew can cause a validation error:
'iat': 20000
Little demo:
@mark-adams: Thanks for the quick response!
I think that the lightest weight change might simply to be to adjust the leeway to be something other than zero by default, and add it as an explicit param to PyJWT.decode, so that it’s easy to find it with introspection tools or in the docstring for that function. As it is, I think that it’s going to be pretty common for people to trip themselves up with the error if they don’t know the external docs by heart.
When choosing between passing a value to leeway or disabling the check, I chose to disable the check. That might be because I’m coming more from a distributed database monkey’s perspective, where I use timestamps to keep a record of things and help servers do things internally, but try to use other methods for syncing state across servers. I may be missing the security implications in skipping the check.
In any case, I think that I’d be happy with either solution – just as long as the solution isn’t to assume that all server clocks are perfectly in sync by default. It definitely makes sense to include it in a later version so that existing code doesn’t break 😃