Skip to content

fix(oauth): match backend access-token prefix for passthrough detection#149

Merged
LifeXplorer merged 3 commits into
mainfrom
avinahradau/fix-oauth-token-prefix-mismatch
Jun 18, 2026
Merged

fix(oauth): match backend access-token prefix for passthrough detection#149
LifeXplorer merged 3 commits into
mainfrom
avinahradau/fix-oauth-token-prefix-mismatch

Conversation

@LifeXplorer

Copy link
Copy Markdown
Contributor

Details

OAuth-passthrough over HTTP transport could not access any real workspace: every hosted-OAuth MCP call to opik-backend was rejected with 403 workspace does not match access token.

Root cause — a token-prefix contract mismatch between issuer and forwarder:

Access-token prefix
opik-backend mints (McpOAuthTokenUtils.ACCESS_PREFIX) opik_mcp_at_
opik-mcp detected OAuth by opik_at_

"opik_mcp_at_…".startswith("opik_at_") is False, so a genuine OAuth bearer was misclassified as a non-OAuth credential in resolve_opik_config. That path forwards a workspace header (inbound Comet-Workspacesettings.comet_workspace"default"). With no inbound header and no COMET_WORKSPACE set, it sent Comet-Workspace: default.

opik-backend's AuthFilter/McpOAuthService does recognize the token (it checks opik_mcp_at_), reads the workspace bound to the token row in mcp_oauth_tokens, and the guard workspaceMatches(header, tokenWorkspace) rejects the non-blank, mismatched default header. Since default can never be a token's workspace (consent rejects it), OAuth-passthrough was fully broken for hosted deployments.

Fix: consolidate the prefix into a single OAUTH_ACCESS_TOKEN_PREFIX = "opik_mcp_at_" constant in auth_context and use it at both detection sites — classify_bearer (BI analytics) and resolve_opik_config (outbound forwarding) — so they match the issuer and cannot drift from each other again. With detection fixed, OAuth bearers take the passthrough branch, workspace is None, the Comet-Workspace header is omitted, and opik-backend derives the workspace from the token row.

Note: opik-mcp never handles refresh tokens (the MCP host owns the OAuth dance), so the access-token prefix was the only mismatch; the backend's opik_mcp_rt_ refresh prefix is not referenced here.

Change checklist

  • User facing
  • Documentation updated (if needed)
  • Tests added/updated (if needed)
  • Breaking changes documented (if any)

Issues

  • Resolves #
  • OPIK-

Testing

  • Updated all tests that hardcoded the opik_at_ token shape to opik_mcp_at_ so prefix-dependent assertions (auth_mode == "oauth", workspace omitted, token_sha256 present) exercise the real issuer prefix. The "marker mid-string is not a prefix" test (sk-xopik_mcp_at_y) still asserts the API-key fallback.
  • Recommend a follow-up integration check against a dev opik-backend OAuth token to confirm list project returns 200.

Documentation

  • README and module docstrings/comments updated from opik_at_… to opik_mcp_at_….

LifeXplorer and others added 3 commits June 18, 2026 16:32
opik-backend mints MCP OAuth access tokens with the prefix `opik_mcp_at_`
(McpOAuthTokenUtils.ACCESS_PREFIX), but opik-mcp's OAuth-passthrough
detection checked for `opik_at_`. The two never matched, so every real
OAuth bearer was misclassified as a non-OAuth credential in
resolve_opik_config: it took the API-key branch and forwarded a stale
Comet-Workspace header (settings.comet_workspace, else "default").
opik-backend's AuthFilter — which recognizes the token correctly — then
rejected the request with 403 "workspace does not match access token",
because the token row is bound to the consented workspace and is never
"default". Result: hosted OAuth MCP could not access any real workspace.

Consolidate the prefix into a single OAUTH_ACCESS_TOKEN_PREFIX constant in
auth_context and use it at both detection sites (classify_bearer and
resolve_opik_config) so they can't drift again. Update comments, README,
and tests to the correct prefix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ead of the raw literal

Keep the raw "opik_mcp_at_" string in exactly one place — the constant
definition — and have every other src/ comment and docstring refer to it
by name. Single source of truth; no functional change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Import the constant in the auth/analytics tests and construct sample
bearers/canaries as f-strings from it, so the suite tracks the prefix
automatically and the raw "opik_mcp_at_" string survives only in the
constant definition (README left as user-facing docs). No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@LifeXplorer LifeXplorer merged commit 9a378e2 into main Jun 18, 2026
4 checks passed
@LifeXplorer LifeXplorer deleted the avinahradau/fix-oauth-token-prefix-mismatch branch June 18, 2026 15:55
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