Skip to content

Fix JWTVerifier null expected values and builder reuse#785

Open
asim-alam wants to merge 2 commits into
auth0:masterfrom
asim-alam:fix/jwtverifier-null-value-and-builder-reuse
Open

Fix JWTVerifier null expected values and builder reuse#785
asim-alam wants to merge 2 commits into
auth0:masterfrom
asim-alam:fix/jwtverifier-null-value-and-builder-reuse

Conversation

@asim-alam

Copy link
Copy Markdown

Changes

Two related robustness fixes in JWTVerifier, each covered by a new test that fails on
master and passes with this change. No public API change.

1. Null expected value threw a raw NullPointerException (should be IncorrectClaimException).
Registering a null expected value (e.g. withSubject(null), withJWTId(null),
withClaim(name, (Integer) null)) is the documented way to require a claim to be JSON null.
But when the token actually carried the claim, the code called value.equals(claim.asX()) on the
null expected value, leaking a NullPointerException out of verify(). Fixed by using the null-safe
Objects.equals(value, claim.asX()) at every value-claim site. Behaviour is identical when the value
is non-null; when it is null and the claim is present, verification now fails with
IncorrectClaimException.

2. Reused builders shared and retroactively mutated verifier state.
The constructor wrapped the builder's live ArrayList with Collections.unmodifiableList (a view,
not a copy), and build(Clock) appended the mandatory exp/nbf/iat checks to that same list.
Since a built verifier is documented as reusable, calling build() twice appended a second set of
mandatory checks and retroactively mutated the first, already-returned verifier. Fixed by building
the mandatory checks into a fresh list inside build() (making it idempotent) and taking a defensive
copy in the constructor. Single-build behaviour and all existing check counts are unchanged.

References

Found by manual code review - no static-analysis rule flags either issue.

Testing

  • Added JWTVerifierTest.shouldThrowIncorrectClaimNotNpeWhenNullSubjectExpectedButClaimPresent and
    JWTVerifierTest.shouldBuildReusableVerifierWithoutDuplicatingMandatoryChecks. Both fail on
    master and pass with this change.

  • Ran the full module suite with ./gradlew :java-jwt:test: 681 tests, 0 failures.

  • This change adds unit test coverage

  • This change has been tested on the latest version of the platform/language

Checklist

  • I have read the Auth0 general contribution guidelines
  • I have read the Auth0 Code of Conduct
  • All existing and new tests complete without errors

asim-alam added 2 commits July 4, 2026 02:13
Registering a null expected value (e.g. withSubject(null), withJWTId(null),
withClaim(name, (Integer) null)) documents the intent "this claim must be JSON
null". When the token actually carried the claim, verifyNull() returned false and
the code then called value.equals(claim.asX()) on the null expected value, leaking
a raw NullPointerException out of verify() instead of the documented
IncorrectClaimException.

Use the null-safe Objects.equals(value, claim.asX()) at every value-claim site
(withSubject, withJWTId, the typed withClaim overloads, and the Instant overload,
which withClaim(Date) delegates to). Behaviour is identical when the value is
non-null; when it is null and the claim is present, verification now fails with
IncorrectClaimException. No public API change.
The JWTVerifier constructor wrapped the builder's live ArrayList with
Collections.unmodifiableList (a view, not a copy), and build(Clock) appended the
mandatory exp/nbf/iat checks to that same list. Calling build() twice on one
BaseVerification therefore appended a second set of mandatory checks AND
retroactively mutated the first, already-returned (and documented as immutable and
thread-safe) verifier.

Build the mandatory checks into a fresh list inside build() instead of mutating the
builder's field, and take a defensive copy in the constructor. build() is now
idempotent and each verifier owns an independent snapshot. Single-build behaviour
and all existing check counts are unchanged.
@asim-alam asim-alam requested a review from a team as a code owner July 3, 2026 20:55
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.

1 participant