Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The token is not yet valid (iat). #814

Closed
AGaliciaMartinez opened this issue Oct 21, 2022 · 22 comments · Fixed by #821
Closed

The token is not yet valid (iat). #814

AGaliciaMartinez opened this issue Oct 21, 2022 · 22 comments · Fixed by #821

Comments

@AGaliciaMartinez
Copy link

We found an issue in one of our tests in the recent update of yours. Our package works with pyjwt==2.5.0 but it breaks with pyjwt==2.6.0. We will give more details on the issue once we explore it more and will try to provide a minimal example that reproduces the error. However, may I ask you to check if the latests changes could have broken backawards compatibility? We are particularly suspicious of #794. We think this because the error message we get: The token is not yet valid (iat).

We are sorry we could not provide more details yet.

System Information

$ python -m jwt.help
{
  "cryptography": {
    "version": "38.0.1"
  },
  "implementation": {
    "name": "CPython",
    "version": "3.10.6"
  },
  "platform": {
    "release": "5.19.16-76051916-generic",
    "system": "Linux"
  },
  "pyjwt": {
    "version": "2.6.0"
  }
}
@sriharan16
Copy link
Contributor

Hi @AGaliciaMartinez ,
Kindly check the token you use whether the issued_at time is greater than current (adding leeway as well). This is the case which was handled as part of this Pull Request .

I request you take a look at the PR description to understand it better why we need to include this check and also check your code/token what's the current_time and issued_at time is to ensure the same.

@jadbin
Copy link

jadbin commented Oct 23, 2022

Different servers could have different time settings, especially when they are not connected to the internet, like a private network. Not every private network has a clock synchronization mechanism.

@sriharan16
Copy link
Contributor

In that cases we can use the UTC time as a standard time. Also for the time difference in clock synchronization we can use the leeway time to make it work.

@auvipy any added inputs will be helpful here

@dsschult
Copy link

I've also encountered this issue, particularly in tests where the token is issued within a few milliseconds of when it gets used. The problem is that the iat is assumed to be an integer, while the RFC allows floats. If a token is issued with a float iat and decoded within that second, the code will test against the integer second, which is less than the float iat. This results in ImmatureSignatureError being raised.

Here is an example that used to succeed, but now raises an error:

jwt.decode(jwt.encode({'iat': time.time()}, 'secret', algorithm='HS256'), 'secret', algorithms=['HS256'])

I suggest either allowing full float calculations or casting the input iat to an int.

@auvipy
Copy link
Collaborator

auvipy commented Oct 25, 2022

In that cases we can use the UTC time as a standard time. Also for the time difference in clock synchronization we can use the leeway time to make it work.

@auvipy any added inputs will be helpful here

we need to get rid of the type error

@sriharan16
Copy link
Contributor

sriharan16 commented Oct 25, 2022

It's yes and no, since most JWT libraries in other languages also support the int type this could cause an issue. I agree with the fact that float has a bit more precision than int but to tackle this synchronization issue the recommended way is leeway time. So adding the minimum leeway time will help them without changing the custom logic they have now.

An example could be,
jwt.decode(jwt.encode({'iat': int(time.time())+leeway}, 'secret', algorithm='HS256'), 'secret', algorithms=['HS256'])

With this, PyJWT will be in sync with other frameworks/libraries in other languages as well.

@dtczest
Copy link

dtczest commented Oct 25, 2022

We've also been hit by this bug, broke all our tests. Our 'iat' stores the current time as a float.

@sriharan16
Copy link
Contributor

Hi,
The check of iat as an integer exisits for a long time. The new change that went in is the ImmatureSignatureError when the iat is greater than current time + leeway.

Screenshot 2022-10-25 at 11 32 32 PM

@auvipy I hope this is something we all need to take a call on.

@dsschult
Copy link

One simple "fix" would be to cast iat to an int, for example:

iat = payload["iat"]
try:
    iat = int(iat)
except ValueError:
    raise InvalidIssuedAtError("Issued At claim (iat) must be an integer.")

This would allow PyJWT to only deal with integers, but be compatible with sources that use floats.

It is possible to use leeway instead, but if that is the approach to be taken, maybe the default should be leeway=1 instead of leeway=0 so existing code continues to work.

@jpadilla
Copy link
Owner

what if instead of:

now = timegm(datetime.now(tz=timezone.utc).utctimetuple())

we just did

now = datetime.now(tz=timezone.utc).timestamp()

Wouldn't that mostly address the issues seen? Anything else would be addressed by specifying a leeway when decoding. unsure if i'm missing anything else.

@daillouf
Copy link
Contributor

daillouf commented Nov 4, 2022

@jpadilla your solution should work,

I also got a few tests failing when upgrading pyjwt :

/usr/local/lib/python3.9/site-packages/jwt/api_jwt.py:193: in _validate_claims
    self._validate_iat(payload, now, leeway)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <jwt.api_jwt.PyJWT object at 0xffff9257b910>
payload = {'access': True, 'event_id': 10, 'exp': 1667564373.7657802, 'iat': 1667564058.7661693, ...}, now = 1667564058
leeway = 0

    def _validate_iat(self, payload, now, leeway):
        iat = payload["iat"]
        try:
            int(iat)
        except ValueError:
            raise InvalidIssuedAtError("Issued At claim (iat) must be an integer.")
        if iat > (now + leeway):
>           raise ImmatureSignatureError("The token is not yet valid (iat)")
E           jwt.exceptions.ImmatureSignatureError: The token is not yet valid (iat)

/usr/local/lib/python3.9/site-packages/jwt/api_jwt.py:219: ImmatureSignatureError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB post_mortem (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /usr/local/lib/python3.9/site-packages/jwt/api_jwt.py(219)_validate_iat()
-> raise ImmatureSignatureError("The token is not yet valid (iat)")
(Pdb) iat
1667564058.7661693
(Pdb) now
1667564058
(Pdb)

the value of «now» seems not to include milliseconds, because

per timegm source code, it’s only seconds :

def timegm(tuple):
    """Unrelated but handy function to calculate Unix timestamp from GMT."""
    year, month, day, hour, minute, second = tuple[:6]
    days = datetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
    hours = days*24 + hour
    minutes = hours*60 + minute
    seconds = minutes*60 + second
    return seconds

we could bypass this with a leeway of 1 second, but that’s a lot of leeway

@CCodeInspect
Copy link

It's yes and no, since most JWT libraries in other languages also support the int type this could cause an issue. I agree with the fact that float has a bit more precision than int but to tackle this synchronization issue the recommended way is leeway time. So adding the minimum leeway time will help them without changing the custom logic they have now.

An example could be, jwt.decode(jwt.encode({'iat': int(time.time())+leeway}, 'secret', algorithm='HS256'), 'secret', algorithms=['HS256'])

With this, PyJWT will be in sync with other frameworks/libraries in other languages as well.

i don't think it's a good solutions.

@pokecheater
Copy link

Hi.
Had the same issue, when updating from 2.4.0 to 2.6.0... drove me crazy this bug.

Solved it by simply deactivate the check with: options={"verify_iat":False} in decode:

self.decoded_token = jwt.decode( token_str, ActivationToken.pub_key, algorithms=["RS256"], options={"verify_iat":False} )
But I have to admit, I have wrote a custom check for the iat already before that update with an higher tolerance value (I think this is what you have labeled as leeway).

@Aditya23456
Copy link

will there be any tagged release with this fix?

@Zuiluj
Copy link

Zuiluj commented Mar 13, 2023

I've also encountered this issue, particularly in tests where the token is issued within a few milliseconds of when it gets used. The problem is that the iat is assumed to be an integer, while the RFC allows floats. If a token is issued with a float iat and decoded within that second, the code will test against the integer second, which is less than the float iat. This results in ImmatureSignatureError being raised.

Here is an example that used to succeed, but now raises an error:

jwt.decode(jwt.encode({'iat': time.time()}, 'secret', algorithm='HS256'), 'secret', algorithms=['HS256'])

I suggest either allowing full float calculations or casting the input iat to an int.

I took this advice and casted the payload I'm passing to 'iat' to int like this:

def create_jwt(cls, secret_claims):

    key = cls.get_key()
    pay_f = Fernet(key.get('payload_key'))
    gmt = tz.gettz('GMT')
    now = datetime.datetime.now(tz=gmt)
    exp = now + datetime.timedelta(days=1)
    payload = {
        'iat': int(now.timestamp()),   # casted to int
        'exp': exp.timestamp(),
        'nbf': now.timestamp(),
        'iss': env('PROJECT_NAME', 'pfunk'),
        'til': pay_f.encrypt(json.dumps(secret_claims).encode()).decode()
    }
    return jwt.encode(payload, key.get('signature_key'), algorithm="HS256", headers={'kid': key.get('kid')}), exp

It worked for me. Thank you! (Although I will still watch this thread)

@niedong
Copy link

niedong commented Mar 14, 2023

I've also encountered this issue, particularly in tests where the token is issued within a few milliseconds of when it gets used. The problem is that the iat is assumed to be an integer, while the RFC allows floats. If a token is issued with a float iat and decoded within that second, the code will test against the integer second, which is less than the float iat. This results in ImmatureSignatureError being raised.

Here is an example that used to succeed, but now raises an error:

jwt.decode(jwt.encode({'iat': time.time()}, 'secret', algorithm='HS256'), 'secret', algorithms=['HS256'])

I suggest either allowing full float calculations or casting the input iat to an int.

Thanks for the great catch. But the problem is that even if now becomes a float from query, it may still have milliseconds inaccuracy with iat. If then it's smaller than iat, the exception would still be thrown. I'm not sure which is the best way to solve this. Perhaps adding leeway in the client code? If that's the case then there's nothing to be modified in the library code. (I'm not sure how other JWT library handle this)

@vergenzt
Copy link

vergenzt commented Dec 1, 2023

Y'all this was litigated previously at #190. The JWT spec does NOT say to reject tokens with iat ("issued at") in the future, so this behavior goes beyond the spec and is inconsistent with many other JWT libraries.

4.1.6. "iat" (Issued At) Claim

The "iat" (issued at) claim identifies the time at which the JWT was
issued. This claim can be used to determine the age of the JWT. Its
value MUST be a number containing a NumericDate value. Use of this
claim is OPTIONAL.

If token issuers want clients to specify that a token should not be accepted before a certain timestamp (which puts additional constraints upon clients by implying that clients' clocks should keep relatively in sync with a central clock source and/or need to check it with leeway) then the issuer is supposed to set nbf ("not before"):

4.1.5. "nbf" (Not Before) Claim

The "nbf" (not before) claim identifies the time before which the JWT
MUST NOT be accepted for processing. The processing of the "nbf"
claim requires that the current date/time MUST be after or equal to
the not-before date/time listed in the "nbf" claim. Implementers MAY
provide for some small leeway, usually no more than a few minutes, to
account for clock skew. Its value MUST be a number containing a
NumericDate value. Use of this claim is OPTIONAL.

I am currently getting bit by this issue a lot in a large enterprise microservices environment (where token issuer, token user, and token-accepting server which validates offline using RSA are three different machines) where clock drift is coming into play. Myself and 10+ other people have now wasted multiple days of person-time trying to figure out why tokens were being rejected, talking about how best to address it, and managing the workloads & deliverables of the people investigating & talking about this.

Could this issue be re-opened and could the proper fix be to set the default value of verify_iat to False and publish a 3.0 since that's a breaking change?

Clients who understand the risks and want to engage in this extra-spec behavior should opt in by setting verify_iat to True, and the need to do this should be announced in the changelog for this new major version. (Or maybe there could be a global variable in pyjwt to control the verify_iat default?)

@vergenzt
Copy link

vergenzt commented Dec 1, 2023

Just for fairness, I did some due diligence on the other libraries. Every other library I've found that does this has its own respective issue complaining about it. 😉

Libraries I found that do not check that iat is >= now:

Libraries that do (and their respective bug complaints):

Maybe this needs to be clarified in the spec since there's a pretty divided polity.......

@vergenzt
Copy link

vergenzt commented Dec 1, 2023

I'm going to be submitting an errata request for RFC 7519 about this. I'll be making my draft of that & centralizing the Github-side discussion here:

Discussion: JWT tokens containing iat values in the future should not be rejected

@Samk13
Copy link

Samk13 commented Mar 13, 2024

Lately, I've been encountering this error The token is not yet valid (iat) (as of March 2024). Interestingly, the solution was simply to resynchronize my computer clock on Windows 11 23H2 (OS build 22631.3296) working on WSL2.
Has anyone else encountered this issue where the clock gets out of sync, as I have experienced it multiple times this week!
My computer is relatively new, so I doubt the MB battery is causing the problem.

@marty0678
Copy link

Lately, I've been encountering this error The token is not yet valid (iat) (as of March 2024). Interestingly, the solution was simply to resynchronize my computer clock on Windows 11 23H2 (OS build 22631.3296) working on WSL2. Has anyone else encountered this issue where the clock gets out of sync, as I have experienced it multiple times this week! My computer is relatively new, so I doubt the MB battery is causing the problem.

@Samk13 I've been chasing an issue with Django Allauth + DJ Rest Auth for the last couple of hours and re-syncing my Windows clock fixed this JWT iat error being raised inside of Allauth. So first, thank you haha, but second yes it appears I am also experiencing the same thing.

DavidLiuGit added a commit to DavidLiuGit/pycognito that referenced this issue Mar 26, 2024
PyJWT v2.8.0 verifies `iat` (issued-at timestamp) by default. There are several discussions on disabling this check, since it is not within spec. [Cognito's token verification guide](https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html#amazon-cognito-user-pools-using-tokens-manually-inspect) does not suggest verifying `iat`, unlike `exp`.

Other discussions:
jpadilla/pyjwt#814
jpadilla/pyjwt#939
@vergenzt
Copy link

See #939

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.