Skip to content

feat(server): add bearer token auth for HTTP transport - #390

Merged
tianzhou merged 4 commits into
mainfrom
feat/http-bearer-token-auth
Jul 31, 2026
Merged

feat(server): add bearer token auth for HTTP transport#390
tianzhou merged 4 commits into
mainfrom
feat/http-bearer-token-auth

Conversation

@tianzhou

@tianzhou tianzhou commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

  • Fixes Add Bearer token authentication for HTTP transport security #66 — DBHub's HTTP transport had no client-facing auth: anyone with the server URL could reach the MCP endpoint.
  • Adds --auth-token / DBHUB_AUTH_TOKEN (comma-separated list of bearer tokens), checked with constant-time comparison on every HTTP request except /healthz. Missing/invalid tokens get 401 + WWW-Authenticate: Bearer.
  • Configuring a token is itself the enforcement switch — no separate --require-auth flag to forget, and unset (the default) leaves today's unauthenticated behavior unchanged.
  • Deliberately a flat shared-secret allow-list rather than the MCP spec's full OAuth 2.1 resource-server model (RFC 9728 protected-resource metadata, dynamic client registration, PKCE) — that machinery solves multi-tenant identity federation, which isn't DBHub's problem here.
  • A comma-separated token list supports zero-downtime rotation (add new, redeploy, drop old) and per-client tokens (revoke one without affecting others).

Test plan

  • pnpm test — full suite (1282 tests) passes, no regressions.
  • New unit tests in src/utils/__tests__/auth-token.test.ts covering: no tokens configured, valid/invalid/missing token, wrong scheme, multi-token list, case sensitivity, length-mismatch tokens.
  • Manual smoke test: started server with --auth-token, confirmed curl without a token gets 401 + WWW-Authenticate: Bearer, wrong token gets 401, /healthz returns 200 unauthenticated, correct token reaches the MCP handler and gets a real initialize response.
  • Manual smoke test: started server without --auth-token, confirmed unauthenticated requests still work (no regression).
  • Docs updated: docs/config/command-line.mdx (new --auth-token section, Quick Reference row, updated stale --host warning) and CLAUDE.md.

🤖 Generated with Claude Code

Anyone with the HTTP server URL could reach the MCP endpoint unauthenticated.
Adds an opt-in --auth-token/DBHUB_AUTH_TOKEN flag: a comma-separated list of
bearer tokens checked with constant-time comparison on every request except
/healthz. Configuring a token is itself the enforcement switch, so there's no
separate flag to forget. Deliberately a flat shared-secret allow-list rather
than full OAuth 2.1 resource-server machinery (RFC 9728 metadata, DCR, PKCE),
matching the pattern used by mcp-remote, Sentry MCP's self-hosted mode, and
the community crystaldba/postgres-mcp nginx template.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 31, 2026 11:34

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.

Pull request overview

This PR adds optional Bearer token authentication for DBHub’s HTTP transport to prevent unauthenticated access to the MCP endpoint (and other HTTP routes) when an auth token is configured.

Changes:

  • Introduces --auth-token / DBHUB_AUTH_TOKEN (comma-separated tokens) and validation logic using constant-time comparison.
  • Enforces Bearer auth on the HTTP Express app for all routes except /healthz, and updates CORS allow-headers to include Authorization.
  • Adds unit tests for token validation and updates CLI documentation to describe the new flag.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/utils/auth-token.ts Adds Bearer token parsing + constant-time allow-list validation utility.
src/utils/tests/auth-token.test.ts Adds unit tests covering configured/unconfigured auth token scenarios and header variants.
src/server.ts Wires token enforcement middleware into HTTP transport and updates CORS allow-headers + startup logging.
src/config/env.ts Adds resolveAuthTokens() to source token list from CLI/env configuration.
docs/config/command-line.mdx Documents --auth-token usage and updates quick reference + production warning.
CLAUDE.md Documents the new --auth-token flag in the command-line options list.

Comment thread src/config/env.ts
Comment thread docs/config/command-line.mdx Outdated
Comment thread src/utils/__tests__/auth-token.test.ts
tianzhou and others added 3 commits July 31, 2026 04:42
Drop the transport-gated ternary around resolveAuthTokens() — it's cheap to
call unconditionally and was duplicating the function's own default-value
literal. Move the /healthz route registration ahead of the auth middleware
instead of hardcoding a path exemption inside it, so the middleware stays a
plain token gate with no awareness of which routes are public.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The design rationale for the shared-secret approach stands on its own; naming
other MCP servers' auth implementations isn't necessary to justify it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Give resolveAuthTokens() its own splitTokenList() instead of reusing the
  host-oriented splitHostList() — decouples token parsing from any future
  host normalization (lowercasing, port-stripping) that must never touch
  case-sensitive tokens.
- Fix the --auth-token docs example: /mcp requires a JSON-RPC POST body, so
  the curl example wasn't copy-pasteable. Switched to /api/sources, a plain
  GET behind the same auth middleware.

Co-Authored-By: Claude Sonnet 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/utils/auth-token.ts:55

  • Authorization scheme parsing is case-sensitive and strictly requires exactly "Bearer ". Per HTTP auth conventions the scheme token is case-insensitive, and extra whitespace after the scheme is common (e.g. "bearer " or "Bearer "). As written, those valid/real-world headers will be rejected as malformed.
  if (!authorizationHeader || !authorizationHeader.startsWith(BEARER_PREFIX)) {
    return {
      ok: false,
      status: 401,
      message: "Missing or malformed Authorization header. Expected: Bearer <token>",

src/utils/tests/auth-token.test.ts:18

  • The validator should accept common header variations (scheme case-insensitivity and extra whitespace) to avoid interop issues across different HTTP stacks. Add unit tests for these cases so the behavior is locked in.
  it('accepts a matching bearer token', () => {
    const result = validateAuthToken('Bearer secret123', ['secret123']);
    expect(result).toEqual({ ok: true });
  });

  it('accepts a token that matches any entry in a multi-token list', () => {
    const tokens = ['first-token', 'second-token', 'third-token'];
    expect(validateAuthToken('Bearer second-token', tokens)).toEqual({ ok: true });
  });

@tianzhou
tianzhou merged commit 99e7672 into main Jul 31, 2026
4 checks passed
@tianzhou tianzhou mentioned this pull request Jul 31, 2026
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.

Add Bearer token authentication for HTTP transport security

2 participants