Repository: jpadilla/pyjwt
Pull request #794 (2022-08-24 17:45:10)

Handling 'ImmatureSignatureError' for issued_at time
Handling 'ImmatureSignatureError' for issued_at time when it is a future time epoch.
```
if iat > (now + leeway):
   raise ImmatureSignatureError("The token is not yet valid (iat)")
```

When the `issued_at` time in the payload is greater than the `current time + leeway` then we can call it out as `ImmatureSignatureError` as we do for `(nbf)`

We have `nbf` in the payload but still, with proper `nbf` someone can call the API with improper `iat` to fool the system.

Example:
```
{
  "nbf": 1661419080   # 25-08-2022 14:48 IST
  "iat": 1661419200,    # 25-08-2022 14:50 IST
  "exp": 1661419500   # 25-08-2022 14:55 IST
}
```

Here the token is valid from 14:48(as per `nbf`) and has an expiry range of 5min from `iat` which makes the token valid. But the `iat` is less than `nbf` which makes the token valid for 7mins instead of 5min. This should not happen as per contract but attackers may do something like this even making `iat` and `exp` with the year 2023 which still makes the token valid.

We can restrict the same way as we do for `nbf`.

sriharan16: @jpadilla Kindly help here in validating and reviewing this PR.
sriharan16: @auvipy I have rebased as mentioned.
Also not sure why requesting your review removed @jpadilla! Kindly verify the changes and also add @jpadilla again as a reviewer

Patch:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 82b7549..7beca10 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -12,7 +12,7 @@ jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v5
+      - uses: actions/stale@v6
         id: stale
         with:
           stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index fb936fd..1a00657 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -13,8 +13,11 @@ Changed
 Fixed
 ~~~~~
 
+- Invalidate token on the exact second the token expires `#797 <https://github.com/jpadilla/pyjwt/pull/797>`_
+
 Added
 ~~~~~
+- Adding validation for `issued_at` when `iat > (now + leeway)` as `ImmatureSignatureError` by @sriharan16 in https://github.com/jpadilla/pyjwt/pull/794
 
 `v2.5.0 <https://github.com/jpadilla/pyjwt/compare/2.4.0...2.5.0>`__
 -----------------------------------------------------------------------
diff --git a/jwt/api_jwt.py b/jwt/api_jwt.py
index 91a6d2e..4bb1ee1 100644
--- a/jwt/api_jwt.py
+++ b/jwt/api_jwt.py
@@ -210,10 +210,13 @@ class PyJWT:
                 raise MissingRequiredClaimError(claim)
 
     def _validate_iat(self, payload, now, leeway):
+        iat = payload["iat"]
         try:
-            int(payload["iat"])
+            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)")
 
     def _validate_nbf(self, payload, now, leeway):
         try:
@@ -230,7 +233,7 @@ class PyJWT:
         except ValueError:
             raise DecodeError("Expiration Time claim (exp) must be an" " integer.")
 
-        if exp < (now - leeway):
+        if exp <= (now - leeway):
             raise ExpiredSignatureError("Signature has expired")
 
     def _validate_aud(self, payload, audience):


Test patch:
diff --git a/tests/test_api_jwt.py b/tests/test_api_jwt.py
index bebe7d2..d74973d 100644
--- a/tests/test_api_jwt.py
+++ b/tests/test_api_jwt.py
@@ -219,6 +219,14 @@ class TestJWT:
         with pytest.raises(InvalidIssuedAtError):
             jwt.decode(example_jwt, "secret", algorithms=["HS256"])
 
+    def test_decode_raises_exception_if_iat_is_greater_than_now(self, jwt, payload):
+        payload["iat"] = utc_timestamp() + 10
+        secret = "secret"
+        jwt_message = jwt.encode(payload, secret)
+
+        with pytest.raises(ImmatureSignatureError):
+            jwt.decode(jwt_message, secret, algorithms=["HS256"])
+
     def test_decode_raises_exception_if_nbf_is_not_int(self, jwt):
         # >>> jwt.encode({'nbf': 'not-an-int'}, 'secret')
         example_jwt = (