Skip to content

@W-19861422 - token endpoint hardening#267

Merged
mattcfilbert merged 3 commits intomainfrom
W-19861422/token-endpoint-hardening
Mar 20, 2026
Merged

@W-19861422 - token endpoint hardening#267
mattcfilbert merged 3 commits intomainfrom
W-19861422/token-endpoint-hardening

Conversation

@mattcfilbert
Copy link
Contributor

@mattcfilbert mattcfilbert commented Mar 17, 2026

Pull Request Template

Description

Two targeted hardening changes to the OAuth token endpoint that enforce standard OAuth 2.1 binding requirements. No new features or breaking changes — purely additive validation.

Change 1: Validate redirect_uri at token exchange
When a client exchanges an authorization code for a token, the redirect_uri in the token request must now match the one used at authorization time. Returns 400 invalid_grant on mismatch.

Change 2: Validate client_id at token exchange (conditional)
When client_id is present in the token request (body or Basic Auth header), it must match the client_id used at authorization. Returns 400 invalid_grant on mismatch. If client_id is absent, the check is skipped — public clients that omit it are unaffected.

Motivation and Context

A security review (W-19861422) identified that the OAuth token endpoint was not enforcing two standard OAuth 2.1 requirements:

  1. redirect_uri binding (OAuth 2.1 Section 4.1.3): The authorization code flow stores redirectUri on the AuthorizationCode object but never compared it at token exchange. An attacker who intercepts a code could exchange it using any redirect URI.

  2. client_id binding: The client_id was stored on AuthorizationCode but similarly never verified at token time. The Basic Auth path was also a gap — a client authenticating via HTTP Basic without a body client_id would bypass the check entirely. The fix uses the credential-verified identity from Basic Auth as a fallback.

PKCE remains the primary protection for public clients. These changes are defense-in-depth that align the implementation with the spec. The broader DCR architectural work remains scoped to the long-term Tableau identity system redesign.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update
  • Other (please describe): OAuth 2.1 compliance / security hardening

How Has This Been Tested?

Five new test cases added to tests/oauth/embedded-authz/oauth.test.ts, all exercising the full authorize → callback → token request flow (not mocked handlers):

  1. redirect_uri mismatch → 400 invalid_grant: 'Redirect URI mismatch'
  2. Matching redirect_uri → 200 success (regression)
  3. Body client_id mismatch → 400 invalid_grant: 'Client ID mismatch'
  4. Absent client_id in token request → 200 success (public client path unaffected)
  5. Basic Auth client_id mismatch (no body client_id) → 400 invalid_grant: 'Client ID mismatch'

All 913 existing unit tests pass locally.

Related Issues

W-19861422

Checklist

  • I have updated the version in the package.json file by using npm run version. For example,
    use npm run version:patch for a patch version bump.
  • I have made any necessary changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have documented any breaking changes in the PR description. For example, renaming a config
    environment variable or changing its default value.

Contributor Agreement

By submitting this pull request, I confirm that:

Verify that the redirect_uri in the token request matches the redirect_uri
used during the original authorization request, per OAuth 2.1 Section 4.1.3.
Adds two test cases: mismatch returns 400 invalid_grant, match succeeds.

Made-with: Cursor
Validates that client_id in the token request matches the client_id used
at authorization, but only when client_id is present (public clients may
omit it). Returns 400 invalid_grant on mismatch.

Made-with: Cursor
…-19861422)

Two OAuth 2.1 compliance fixes that strengthen protection against
authorization code interception without requiring the full DCR redesign:

1. Validate redirect_uri at token exchange (OAuth 2.1 Section 4.1.3)
   The redirect_uri was stored on AuthorizationCode but never checked
   at token time. A mismatched redirect_uri now returns 400 invalid_grant.

2. Validate client_id at token exchange (when provided)
   If client_id is present in the token request (body or Basic Auth),
   it must match the client_id used at authorization. Uses Basic Auth
   credential identity as fallback to close the body-less header path.

Made-with: Cursor
Copy link
Collaborator

@anyoung-tableau anyoung-tableau left a comment

Choose a reason for hiding this comment

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

Thanks Matt! 💪

@mattcfilbert mattcfilbert merged commit 28f7291 into main Mar 20, 2026
6 checks passed
@mattcfilbert mattcfilbert deleted the W-19861422/token-endpoint-hardening branch March 20, 2026 19:09
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