Skip to content

fix: reject tokens missing 'aud' claim when audience is expected - #412

Open
binggao1230 wants to merge 1 commit into
mpdavis:masterfrom
binggao1230:fix-missing-aud-claim-acceptance
Open

fix: reject tokens missing 'aud' claim when audience is expected#412
binggao1230 wants to merge 1 commit into
mpdavis:masterfrom
binggao1230:fix-missing-aud-claim-acceptance

Conversation

@binggao1230

Copy link
Copy Markdown

Problem

jwt.decode(token, key, audience="my-service") silently accepts tokens that carry no aud claim at all. An attacker who holds a valid token from any service that omits aud can replay it against a service that checks audience, because the missing-claim path simply returns without raising.

Minimal reproduction:

from jose import jwt

token = jwt.encode({"sub": "attacker"}, "secret", algorithm="HS256")
# No 'aud' claim in the token

decoded = jwt.decode(token, "secret", algorithms=["HS256"], audience="my-service")
# Returns the claims instead of raising — cross-service replay works

PyJWT raises MissingRequiredClaimError('aud') in the same situation.

The root cause is a commented-out guard in _validate_aud (jwt.py):

if "aud" not in claims:
    # if audience:
    #     raise JWTError('Audience claim expected, but not in claims')
    return   # ← always returns, even when audience= was given

Fix

Activate the guard: raise JWTClaimsError when the caller provides a non-None audience but the token has no aud claim.

The existing options={"require_aud": True} opt-in continues to work as before for callers who need to require the claim without specifying an expected value.

Tests

  • test_aud_empty_claim (which documented the wrong behavior) is replaced by two explicit cases:
    • test_aud_missing_claim_no_audience — no audience expected → still passes
    • test_aud_missing_claim_with_audience — audience given but claim missing → raises JWTError
  • test_require is corrected to not pass an unrelated audience= value when testing require_<other-claim>.

All 250 tests pass (6 skipped, unchanged).

Closes #407


This pull request was prepared with the assistance of AI, under my direction and review.

When jwt.decode() is called with an audience= argument, tokens that do
not carry an 'aud' claim were silently accepted. This enables cross-
service token reuse: a valid token issued without an audience claim
could be replayed against any service that specifies an expected
audience.

The fix activates the commented-out guard in _validate_aud: if the
caller provides a non-None audience and the token has no 'aud' claim,
raise JWTClaimsError instead of returning. The existing require_aud=True
option remains the way to enforce the claim when audience= is not given.

Tests: rename test_aud_empty_claim to two explicit cases (no-audience
passes, audience-given raises), and fix test_require to not pass an
audience value for non-aud required-claim tests.

Fixes mpdavis#407
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant