Skip to content

Harden hosted MCP OAuth docs, challenge semantics, and e2e smoke tooling#179

Open
dodeja wants to merge 1 commit into
mainfrom
codex/data-8540-workos-only-mcp-auth
Open

Harden hosted MCP OAuth docs, challenge semantics, and e2e smoke tooling#179
dodeja wants to merge 1 commit into
mainfrom
codex/data-8540-workos-only-mcp-auth

Conversation

@dodeja
Copy link
Copy Markdown
Contributor

@dodeja dodeja commented Feb 28, 2026

Greptile Summary

This PR implements OAuth 2.1 authentication for the hosted MCP server while maintaining backward compatibility with legacy API token authentication. The implementation adds JWT verification with WorkOS, proper WWW-Authenticate challenge semantics per RFC standards, and comprehensive e2e smoke testing tooling.

Key Changes:

  • Added workos-jwt.ts module with RS256 signature verification, JWKS caching with key rotation support, and comprehensive claim validation (issuer, audience, scope, exp, nbf)
  • Updated MCP handler to detect JWT-like Bearer tokens and verify them via local WorkOS JWT validation or remote internal principal verification fallback
  • Implemented RFC-compliant OAuth challenge response with WWW-Authenticate: Bearer resource_metadata=... headers on 401s
  • Updated TypeScript SDK client to preserve Bearer/Token prefixes for OAuth and legacy tokens
  • Added comprehensive test coverage for OAuth scenarios including challenge headers and internal verification fallback
  • Created bash script for OAuth E2E smoke testing covering discovery, registration, and authenticated MCP calls
  • Added three new documentation files: OAuth requirements specification, test plan with traceability matrix, and E2E smoke test runbook
  • Updated existing docs to position OAuth as the preferred authentication method while documenting legacy compatibility

Security & Quality:

  • JWT verification follows security best practices: algorithm validation (RS256 only), kid validation, signature verification with public keys from JWKS, claim validation (exp, nbf, iss, aud, scope)
  • Dual-mode authentication allows gradual migration from API tokens to OAuth
  • Comprehensive test coverage added for new OAuth flows
  • Proper error handling throughout with graceful fallbacks

The implementation is solid, well-tested, and follows OAuth 2.1 and RFC standards for MCP authentication.

Confidence Score: 5/5

  • This PR is safe to merge with high confidence
  • The implementation follows OAuth 2.1 and JWT security best practices, includes comprehensive test coverage for new scenarios, maintains backward compatibility during migration, and adds extensive documentation. No critical bugs or security vulnerabilities identified. The code is well-structured with proper error handling and follows established patterns.
  • No files require special attention

Important Files Changed

Filename Overview
api/mcp.ts adds OAuth JWT verification with local and remote fallback, WWW-Authenticate challenge headers, and maintains backward compatibility with legacy API tokens
packages/mcp/src/auth/workos-jwt.ts new JWT verification module with RS256 signature validation, JWKS caching, comprehensive claim validation (exp, nbf, iss, aud, scope), and proper error handling
sdks/typescript-sdk/src/client.ts updated to preserve Bearer-prefixed tokens (OAuth JWTs) alongside Token-prefixed legacy API tokens
packages/mcp/tests/api-handler.test.ts added OAuth challenge header validation tests and internal fallback verification test coverage
scripts/mcp-oauth-e2e-smoke.sh new bash script for OAuth smoke testing covering discovery, registration, challenge semantics, and authenticated MCP calls

Sequence Diagram

sequenceDiagram
    participant Client as MCP Client
    participant MCP as MCP Handler
    participant WorkOS as WorkOS JWT
    participant Internal as Internal Verify
    participant Upstream as Terminal49 API

    Note over Client,Upstream: OAuth Flow (JWT Bearer Token)
    Client->>MCP: POST /mcp (no auth)
    MCP->>Client: 401 + WWW-Authenticate header
    Note right of Client: Discovery + OAuth flow happens<br/>(external to this PR)
    Client->>MCP: POST /mcp (Bearer JWT)
    MCP->>MCP: Detect JWT-like Bearer token
    MCP->>WorkOS: Verify JWT locally
    alt Local JWT valid
        WorkOS->>MCP: Valid (user_id, account_id)
        MCP->>Upstream: Call API with Bearer token
        Upstream->>MCP: Response
        MCP->>Client: 200 OK
    else Local JWT invalid
        WorkOS->>MCP: Invalid
        MCP->>Internal: Verify via internal endpoint
        alt Internal verify succeeds
            Internal->>MCP: Valid (active, user_id, account_id)
            MCP->>Upstream: Call API with Bearer token
            Upstream->>MCP: Response
            MCP->>Client: 200 OK
        else Both verifications fail
            Internal->>MCP: Invalid
            MCP->>Client: 401 + WWW-Authenticate
        end
    end

    Note over Client,Upstream: Legacy Flow (API Token)
    Client->>MCP: POST /mcp (Token <api_token>)
    MCP->>MCP: Not JWT-like, check client secret
    MCP->>Upstream: Call API with configured token
    Upstream->>MCP: Response
    MCP->>Client: 200 OK
Loading

Last reviewed commit: d740f6c

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
api Ready Ready Preview, Comment Feb 28, 2026 4:50pm

Request Review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d740f6c30f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread api/mcp.ts
let authSource: 'authorization' | 'environment' | 'oauth_local' | 'oauth_remote' =
resolvedAuth.source ?? 'authorization';

const jwtLikeBearerToken = callerScheme === 'bearer' && looksLikeJwt(callerToken);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Accept opaque Bearer tokens in OAuth validation path

The OAuth branch is gated on looksLikeJwt, so only Bearer tokens with exactly three dot-separated segments are treated as OAuth tokens. Any valid opaque OAuth access token will skip this path and be handled as legacy auth, which means it is rejected as Invalid client credentials whenever T49_API_TOKEN/T49_MCP_CLIENT_SECRET is configured (or forwarded upstream as a Token scheme when not configured). This breaks hosted OAuth clients that receive opaque access tokens.

Useful? React with 👍 / 👎.

}
}

const response = await fetch(jwksUrl, {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle JWKS fetch failures as auth failures

fetchJwks performs network I/O without error handling, so a transient JWKS network error (DNS, timeout, connection reset) throws out of verifyWorkosJwt instead of returning null. In api/mcp.ts, that propagates to the top-level catch and returns HTTP 500, which both skips internal token verification fallback and turns a token-validation issue into a server error for clients.

Useful? React with 👍 / 👎.

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