Skip to content

Reject boolean exp, nbf and iat claims - #1233

Open
ivmat wants to merge 2 commits into
jpadilla:masterfrom
ivmat:reject-bool-numericdate
Open

ivmat wants to merge 2 commits into
jpadilla:masterfrom
ivmat:reject-bool-numericdate

Conversation

@ivmat

@ivmat ivmat commented Oct 17, 2026 •

Copy link
Copy Markdown

decode() accepts JSON booleans in exp, nbf and iat. Python's bool is a subclass of int, so True becomes 1 and False becomes 0. On current master:

key = "a" * 32
token = jwt.encode({"nbf": True}, key, algorithm="HS256")
jwt.decode(token, key, algorithms=["HS256"])  # returns {'nbf': True}

A boolean nbf or iat is accepted. A boolean exp raises ExpiredSignatureError. RFC 7519 §2 defines NumericDate as a JSON number, and a boolean is not a number.

In decode(), this PR checks for bool before the integer conversion. It raises the same errors as #1186 does for other non-numeric values. Numeric strings are still accepted (#847). When verification of the claim is disabled, decoding works as before. The change merges cleanly with #1191.

encode() now raises TypeError for a boolean exp, nbf or iat, as it does for a non-string iss (#1039). On this branch, the encode() line of the example above raises TypeError.

Tests: 15 new cases: 6 for encode(), 6 for decode(), 3 with verification disabled. The boolean-claim decode() tests serialize the payload with json.dumps and sign it with jwt.api_jws.encode. The full suite passes on Python 3.9 and 3.14.

How it was found: a check of pyjwt against JOSE RFC requirements in the acceptance format. The fix rests on these rows:
NumericDate JOSE-7519-2-1,
exp JOSE-7519-4.1.4-2,
nbf JOSE-7519-4.1.5-2, and
iat JOSE-7519-4.1.6-1.

Use the existing claim-specific errors before integer conversion.
Cover boolean values and disabled claim verification.
@ivmat
ivmat force-pushed the reject-bool-numericdate branch from 19dd77c to 17060ce Compare October 17, 2026 13:28

@TheSingulant TheSingulant left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that a JSON boolean isn't a NumericDate, and checking before the int() conversion is the right spot. I ran the new tests against master's api_jwt.py: all six boolean cases fail there and pass here, including exp, where the message match is what separates the new error from ExpiredSignatureError. One question about the encode side.

Comment thread tests/test_api_jwt.py Outdated
def test_decode_rejects_boolean_numericdate(
self, jwt: PyJWT, claim: str, error: type[Exception], value: bool
) -> None:
token = jwt.encode({claim: value}, "a" * 32, algorithm="HS256")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line shows the other half of the issue: jwt.encode() still issues a token with a boolean exp/nbf/iat that decode() now refuses (I checked: encode({"exp": True}, ...) succeeds, and decoding it raises the new DecodeError). encode() already rejects a non-string iss with TypeError (the #1039 check), so rejecting booleans in the time claims there seems consistent. Is that in scope here, or better as a follow-up? If it lands, these tests would need to build the token without going through jwt.encode, e.g. by signing a hand-serialized payload with jwt.api_jws.encode.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, good catch — added to this PR. encode() now raises TypeError for bool exp/nbf/iat (same as iss in #1039), and the decode tests sign the payload with jwt.api_jws.encode as you suggested

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this looks good. Rejecting bools at encode time keeps encode and decode consistent, and building the decode tokens through api_jws.encode means those tests now cover the decode-side check directly. The is True assertion in the verification-disabled case is a nice touch. LGTM from my side.

Raise TypeError, as for a non-string iss (jpadilla#1039).
Build the decode test tokens with jwt.api_jws.encode.

This branch has not been deployed

No deployments
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 this pull request may close these issues.

2 participants