fix(oauth): match backend access-token prefix for passthrough detection#149
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
McpOAuthTokenUtils.ACCESS_PREFIX)opik_mcp_at_opik_at_"opik_mcp_at_…".startswith("opik_at_")isFalse, so a genuine OAuth bearer was misclassified as a non-OAuth credential inresolve_opik_config. That path forwards a workspace header (inbound Comet-Workspace→settings.comet_workspace→"default"). With no inbound header and noCOMET_WORKSPACEset, it sentComet-Workspace: default.opik-backend's
AuthFilter/McpOAuthServicedoes recognize the token (it checksopik_mcp_at_), reads the workspace bound to the token row inmcp_oauth_tokens, and the guardworkspaceMatches(header, tokenWorkspace)rejects the non-blank, mismatcheddefaultheader. Sincedefaultcan 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 inauth_contextand use it at both detection sites —classify_bearer(BI analytics) andresolve_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,workspaceisNone, theComet-Workspaceheader 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
Issues
Testing
opik_at_token shape toopik_mcp_at_so prefix-dependent assertions (auth_mode == "oauth", workspace omitted,token_sha256present) 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.list projectreturns 200.Documentation
opik_at_…toopik_mcp_at_….