Skip to content

Answer 401 to a JWT whose header is not an object - #686

Open
koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:answer_401_to_a_jwt_whose_header_is_not_an_object
Open

koic wants to merge 1 commit into
modelcontextprotocol:mainfrom
koic:answer_401_to_a_jwt_whose_header_is_not_an_object

Conversation

@koic

@koic koic commented Nov 14, 2026

Copy link
Copy Markdown
Member

Motivation and Context

JWTVerifier answered a token whose JOSE header decodes to an array, a number, a boolean or null with an error that is not a JWT::DecodeError: the jwt gem reads members of the header before it has checked that the header is an object, in the unverified decode validate_time_claims! runs or in the verifying decode, depending on the gem version. verify rescues only the gem's own errors, so OAuth::Middleware and the Streamable HTTP transport answered 500 server_error and called the exception reporter where a malformed token gets 401 invalid_token. Such a token needs no key.

validate_time_claims! now refuses a token whose header or payload is not an object as an invalid token, before the verifying decode sees it.

How Has This Been Tested?

test/mcp/server/oauth/jwt_verifier_test.rb. Against the previous library the new test fails with TypeError. Checked by hand with jwt 2.7.1, 2.8.2, 2.10.1 and 3.3.0: each of the headers in the test is answered as an invalid token.

Breaking Changes

None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

`JWTVerifier` answered a token whose JOSE header decodes to an array, a number, a boolean or `null` with an error
that is not a `JWT::DecodeError`: the jwt gem reads members of the header before it has checked that the header is an object,
in the unverified decode `validate_time_claims!` runs or in the verifying decode, depending on the gem version.
`verify` rescues only the gem's own errors, so `OAuth::Middleware` and the Streamable HTTP transport answered 500 `server_error`
and called the exception reporter where a malformed token gets 401 `invalid_token`. Such a token needs no key.

`validate_time_claims!` now refuses a token whose header or payload is not an object as an invalid token,
before the verifying decode sees it.

## How Has This Been Tested?

`test/mcp/server/oauth/jwt_verifier_test.rb`. Against the previous library the new test fails with `TypeError`.
Checked by hand with jwt 2.7.1, 2.8.2, 2.10.1 and 3.3.0: each of the headers in the test is answered as an invalid token.

## Breaking Changes

None.

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.

1 participant