v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
MEDIUM AI PREVALENCE: HIGH auth.py.jwt.no-expiration

A JWT is decoded with options={"verify_exp": False}, which turns off PyJWT's exp (expiration) check.

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

With expiry verification disabled, an expired (or stolen and long-since-revoked) token is still accepted, so tokens effectively never expire.

Remove the "verify_exp": False option; PyJWT verifies exp by default, e.g. jwt.decode(token, key, algorithms=["RS256"]). If a token legitimately carries no exp, prefer options={"require": ["exp"]} to mandate one.

VULNERABLE
vulnerable.py
import jwt
from jwt import decode


def read_no_exp(token: str, key: str):
    # ruleid: auth.py.jwt.no-expiration
    return jwt.decode(token, key, algorithms=["RS256"], options={"verify_exp": False})


def read_no_exp_mixed(token: str, key: str):
    # ruleid: auth.py.jwt.no-expiration
    return jwt.decode(token, key, algorithms=["HS256"], options={"verify_aud": False, "verify_exp": False})


def read_no_exp_destructured(token: str, key: str):
    # ruleid: auth.py.jwt.no-expiration
    return decode(token, key, algorithms=["RS256"], options={"verify_exp": False})
SAFE
safe.py
import jwt


# ok: auth.py.jwt.no-expiration -- default behaviour verifies `exp`
def read_default(token: str, key: str):
    return jwt.decode(token, key, algorithms=["RS256"])


# ok: auth.py.jwt.no-expiration -- expiry verification explicitly enabled
def read_verify_exp_on(token: str, key: str):
    return jwt.decode(token, key, algorithms=["RS256"], options={"verify_exp": True})


# ok: auth.py.jwt.no-expiration -- a different option is disabled, not `verify_exp`
def read_other_option(token: str, key: str):
    return jwt.decode(token, key, algorithms=["RS256"], options={"verify_aud": False})

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

# oauthlint-disable-next-line auth.py.jwt.no-expiration -- <reason>

References

https://pyjwt.readthedocs.io/en/stable/api.html#jwt.decode ↗https://cwe.mitre.org/data/definitions/613.html ↗