Skip to content

fix: reject malformed Content-Type headers with invalid_request - #511

Open
dhensby wants to merge 2 commits into
node-oauth:masterfrom
dhensby:fix/token-handler-invalid-content-type
Open

dhensby wants to merge 2 commits into
node-oauth:masterfrom
dhensby:fix/token-handler-invalid-content-type

Conversation

@dhensby

@dhensby dhensby commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Summary

A request with a Content-Type header whose media type is empty, such as ;charset=utf-8, makes type-is (2.1.0) throw TypeError: argument string is required. Request#is() lets that error escape, so:

  • TokenHandler#handle() rejects with the raw TypeError and not an OAuth error. Its request.is('application/x-www-form-urlencoded') check runs before the try block, so the error is never normalised.
  • AuthenticateHandler#handle(), when the access token is sent in the body, wraps the TypeError in a ServerError (503) for what is a client error.

This PR makes Request#is() treat a Content-Type that type-is cannot parse as not matching (false). That is already how it treats a missing or otherwise invalid type. Both handlers now reject the request with InvalidRequestError (400): Invalid request: content must be application/x-www-form-urlencoded, the same as for any other wrong content type.

I fixed it in Request#is(), not by moving the TokenHandler check into its try block. Moving the check would only turn the raw TypeError into a 503, and it would leave AuthenticateHandler unfixed.

Linked issue(s)

None. Found while reviewing other open PRs.

Involved parts of the project

  • lib/request.js: Request#is()
  • Token endpoint (TokenHandler) and bearer-token-in-body authentication (AuthenticateHandler)

Added tests?

Yes. Each of these fails on master with TypeError: argument string is required and passes here:

  • Request integration › is() › should return false if the content type is malformed
  • TokenHandler integration › handle() › should throw an error if the media type is malformed
  • AuthenticateHandler integration › getTokenFromRequestBody() › should throw an error if the media type is malformed

biome check . is clean and the full mocha suite passes (467).

OAuth2 standard

RFC 6749 §3.2 requires application/x-www-form-urlencoded for token requests, and RFC 6750 §2.2 requires it for bearer tokens in the body. A malformed Content-Type is a malformed request, so it gets invalid_request (RFC 6749 §5.2 / RFC 6750 §3.1), not a server error.

Reproduction

const { Request, Response } = require('@node-oauth/oauth2-server');
const TokenHandler = require('@node-oauth/oauth2-server/lib/handlers/token-handler');

const handler = new TokenHandler({
  accessTokenLifetime: 120,
  refreshTokenLifetime: 120,
  model: { getClient() {}, saveToken() {}, getUser() {} },
});
const request = new Request({
  method: 'POST',
  query: {},
  body: { grant_type: 'password' },
  headers: { 'content-type': ';charset=utf-8', 'transfer-encoding': 'chunked' },
});

await handler.handle(request, new Response());
// master:  TypeError: argument string is required
// this PR: InvalidRequestError: Invalid request: content must be application/x-www-form-urlencoded

🤖 Generated with Claude Code

`type-is` throws a `TypeError` ("argument string is required") for a
Content-Type header with an empty media type, such as `;charset=utf-8`.
`Request#is()` let that error escape, so:

- `TokenHandler#handle()` rejected with the raw `TypeError` because the
  content-type check runs before its error handling, and
- `AuthenticateHandler#handle()` turned it into a `ServerError` (503) when
  the access token was sent in the request body.

`Request#is()` now treats a Content-Type that cannot be parsed as not
matching, so both handlers reject the request with an
`InvalidRequestError` (400) like any other wrong content type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The localized fix preserves successful matching and includes targeted regression coverage, with no unresolved findings.

0 open findings

What changed in this PR

Makes malformed Content-Type headers follow existing OAuth validation paths, producing invalid_request instead of unexpected errors.

Changes:

  • Returns false when Content-Type parsing throws in Request#is().
  • Adds regression coverage for request matching and both affected handlers.
File Description
test/​integration/​request_test.js Tests malformed Content-Type matching.
test/​integration/​handlers/​token-handler_test.js Tests token endpoint rejection.
test/​integration/​handlers/​authenticate-handler_test.js Tests body-token validation rejection.
lib/​request.js Converts parsing exceptions into non-matches.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@dhensby
dhensby marked this pull request as ready for review October 15, 2026 14:39
@dhensby dhensby added this to the 5.4 milestone Oct 15, 2026
@dhensby
dhensby requested a review from jankapunkt October 15, 2026 14:51

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.

3 participants