Skip to content

Add duplicate claim name detection per RFC 7519 Section 4#713

Open
ydah wants to merge 10 commits intojwt:mainfrom
ydah:duplicate_claim_detection
Open

Add duplicate claim name detection per RFC 7519 Section 4#713
ydah wants to merge 10 commits intojwt:mainfrom
ydah:duplicate_claim_detection

Conversation

@ydah
Copy link
Contributor

@ydah ydah commented Jan 29, 2026

Description

Implements duplicate claim name detection as specified in RFC 7519 Section 4, which states:

The Claim Names within a JWT Claims Set MUST be unique; JWT parsers MUST either reject JWTs with duplicate Claim Names or use a JSON parser that returns only the lexically last duplicate member name.

see: https://datatracker.ietf.org/doc/html/rfc7519#section-4

This feature allows users to reject JWTs that contain duplicate keys in the header or payload, which is recommended for security-sensitive applications to prevent claim confusion attacks.

Checklist

Before the PR can be merged be sure the following are checked:

  • There are tests for the fix or feature added/changed
  • A description of the changes and a reference to the PR has been added to CHANGELOG.md. More details in the CONTRIBUTING.md

@ydah ydah force-pushed the duplicate_claim_detection branch from 0838041 to bf095f9 Compare January 29, 2026 13:17
@anakinj
Copy link
Member

anakinj commented Jan 29, 2026

Overall I support this feature and think it should be the default behavior.

My biggest concern is adding a lot of logic to this now it will stay and live in the gem in the future.

Would almost just like to require json >= 2.13.0 and always parse the json with allow_duplicate_key: false. Then ship a jwt gem with this potentially breaking change. If needed we can then add a feature to loosen this behavior by for example allowing swapping the JSON parser.

@ydah ydah force-pushed the duplicate_claim_detection branch from 1acb9e3 to 11450ac Compare January 30, 2026 10:00
@ydah ydah requested a review from anakinj January 30, 2026 10:01
os:
- ubuntu-latest
ruby:
- "2.5"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

MEMO: We added json gem >= 2.13.0 to the dependencies, but this version requires Ruby 2.7+. Since ruby-jwt has required_ruby_version = ‘>= 2.5’, dependency resolution fails during bundle install in a Ruby 2.5 environment.

Copy link
Member

Choose a reason for hiding this comment

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

Would like to support as many versions as possible, what about dynamically figure out if its possible to enable this feature or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, I've restored support for 2.5 and 2.6.

@ydah
Copy link
Contributor Author

ydah commented Jan 30, 2026

I updated this PR. WDYT?

lib/jwt/json.rb Outdated
# JWT::JSON.parse('{"a":1,"a":2}', allow_duplicate_keys: false)
# # => raises JWT::DuplicateKeyError
def parse(data, allow_duplicate_keys: true)
::JSON.parse(data, allow_duplicate_key: allow_duplicate_keys)
Copy link
Member

@anakinj anakinj Feb 3, 2026

Choose a reason for hiding this comment

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

This is problematic in a way that when JSON 3.0 ships with the switched default we still are allowing duplicate keys by default. Would rather at that point enforce the new default for everyone.

Could we pass the parameter to the JSON lib only if the parameter is false,

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I fixed it. Please point out if I understand something wrong.

# @return [self]
# @raise [JWT::DuplicateKeyError] if duplicate keys are found during subsequent parsing.
def raise_on_duplicate_keys!
@allow_duplicate_keys = false
Copy link
Member

@anakinj anakinj Feb 3, 2026

Choose a reason for hiding this comment

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

Could we here detect if the JSON gem is supporting the feature and raise if we are not compatible? Would allow us to support older Ruby versions still.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It sounds good! I modified it that way. WDYT?

# encoded_token.verify_signature!(algorithm: 'HS256', key: 'secret')
# encoded_token.payload # => {'pay' => 'load'}
class EncodedToken
class EncodedToken # rubocop:disable Metrics/ClassLength
Copy link
Member

Choose a reason for hiding this comment

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

Wonder if we could do something to not need to disable cops 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've extracted some classes. WDYT?

@ydah ydah force-pushed the duplicate_claim_detection branch from 225ddb0 to 6ef1d19 Compare February 4, 2026 22:26
ydah added 10 commits February 5, 2026 07:34
Implements duplicate claim name detection as specified in RFC 7519 Section 4, which states:

> The Claim Names within a JWT Claims Set MUST be unique; JWT parsers MUST either reject JWTs with duplicate Claim Names or use a JSON parser that returns only the lexically last duplicate member name.

This feature allows users to reject JWTs that contain duplicate keys in the header or payload, which is recommended for security-sensitive applications to prevent claim confusion attacks.
…on a runtime feature check instead of a hard dependency on json >= 2.13.0
… SignatureVerifier classes to reduce class complexity
@ydah ydah force-pushed the duplicate_claim_detection branch from 905c978 to ad53ab7 Compare February 4, 2026 22:34
@ydah ydah requested a review from anakinj February 4, 2026 22:44
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