Overview
When Bifrost acts as an MCP Gateway, external MCP clients connect to its/mcp endpoint. This page covers how those inbound clients authenticate to Bifrost.
There are two ways a client can present itself:
- Header credentials — a virtual key, API key, or session token sent as a request header. Simple to script, ideal for backend and machine-to-machine use.
- OAuth 2.1 — Bifrost acts as an OAuth authorization server, and the client connects through a browser consent flow, receiving a short-lived JWT. Ideal for interactive clients like Claude Desktop, Claude Code, or Cursor, where pasting a raw key into client config is awkward.
This page is about authenticating clients to Bifrost. For how Bifrost authenticates to upstream MCP servers it connects to, see the outbound Authentication guides instead — that’s the opposite direction.
Authentication Modes
A single setting,mcp_server_auth_mode, controls which credential types /mcp accepts:
| Mode | Header credentials (VK / api-key / session) | Bifrost-issued JWT | Discovery endpoints |
|---|---|---|---|
headers (default) | Accepted | — | Disabled |
both | Accepted | Accepted | Enabled |
oauth | Rejected | Accepted | Enabled |
headers—/mcpaccepts header credentials only. The OAuth surface and.well-knowndiscovery endpoints are not served.both—/mcpaccepts header credentials and Bifrost-issued JWTs. Discovery is served so OAuth clients can connect.oauth—/mcpaccepts Bifrost-issued JWTs only; header credentials are rejected.
x-bf-vk, X-Api-Key, or a Bearer VK) arrive on the same request, Bifrost rejects it with a conflicting credentials error — even in both mode. both means either credential is accepted, not both at once.
How the OAuth Connect Flow Works
Whenmcp_server_auth_mode is both or oauth, Bifrost is a full OAuth 2.1 authorization server for the /mcp resource. A client that doesn’t yet have a token discovers the server, registers itself, and walks the user through a browser consent step.
The flow follows current OAuth standards so off-the-shelf MCP clients work without custom code:
- Protected resource metadata (RFC 9728) — the
401response points clients at the discovery documents. - Dynamic Client Registration (RFC 7591) — clients self-register; no manual client setup.
- PKCE (S256) — public clients authenticate without a shared secret.
- Resource indicators (RFC 8707) — tokens are bound to the
/mcpresource via theaudclaim.
Identity Modes at Consent
During the consent step, Bifrost shows the user how they can identify themselves. The page header names the connecting client — for example “Claude Code wants to connect” — and offers the modes available for your deployment:
| Mode | Binds the grant to | Availability |
|---|---|---|
| Virtual key | A virtual key you paste on the consent page | Always available |
| Session | A server-minted, anonymous session identity | Only when enforce_auth_on_inference is false |
| User | Your signed-in dashboard user | Requires SSO / SCIM |
- Virtual key carries the same governance (budgets, rate limits, tool scoping) the key already has — the JWT simply represents that key.
- Session is an anonymous identity for development and open deployments. It is unavailable once
enforce_auth_on_inferenceis on. - User binds the grant to the authenticated person, so per-user upstream tool authorizations unify under one identity.
User mode requires SSO/SCIM (enterprise). When no identity provider is configured, the consent page offers only virtual key and session modes.
Configuration
- Web UI
- API
- config.json
- Open Config and go to the MCP settings.
-
Set MCP Server Auth Mode to
headers,both, oroauth. -
When using
bothoroauth, optionally set the OAuth Server settings: an Issuer URL (required for multi-host deployments), and the Authorization Code and Access Token lifetimes. -
Click Save.

oauth2_server_config only applies when mcp_server_auth_mode is both or oauth. The RSA signing key used for JWTs is generated automatically the first time it’s needed — no setup required. It is persisted to the database, so it survives restarts and is shared across all replicas; previously issued tokens stay valid after a restart.Connecting a Client
Withboth or oauth enabled, point the MCP client at Bifrost’s /mcp URL — for example https://bifrost.example.com/mcp. No key needs to be pasted into the client config.
- The client hits
/mcp, gets a401, and discovers the authorization server. - It registers itself and opens a browser to the consent page.
- The user chooses an identity (virtual key, session, or user).
- The client receives a token and connects; aggregated tools become available.
Managing Grants
Each completed OAuth connection is a grant — a refresh-token lineage Bifrost issued to a client. The OAuth Grants page lists them: the client, the bound identity, when the grant was created, and when it was last used, with a Revoke action.
A revoked grant’s current access token is a short-lived JWT that keeps working on
/mcp until it expires (up to access_token_ttl, default 600 seconds). After that the client is fully cut off and must reconnect through the consent flow.This is deliberate: a holder of the virtual key or user credentials can always start a new authorized session, so invalidating the access token mid-flight adds little security while costing a per-request lookup. Lower access_token_ttl for a tighter window.The OAuth Grants page lists credentials Bifrost issued to clients for inbound
/mcp access. This is distinct from MCP Sessions, which tracks per-user credentials Bifrost holds for upstream MCP servers.Token Lifetime & Revocation
- Access tokens are JWTs valid for
access_token_ttl(default 600s). On every/mcprequest Bifrost validates the JWT — signature, expiry, issuer, and audience — and confirms the bound identity (the virtual key or user) still exists and is active. The identity check reads an in-memory cache, so in the common case it adds no database round-trip (vk-mode falls back to a single store lookup only on a cache miss). - Refresh tokens rotate on each use and have no fixed expiry. A token is invalidated by rotation, by the bound virtual key or user becoming inactive or deleted, or by an explicit revoke.
- An explicit revoke marks the grant’s refresh-token row revoked, so renewals stop immediately — but it does not remove the bound identity, so the identity check still passes and the already-issued access token keeps working until it expires. Lower
access_token_ttlto shorten that window. - Deleting the bound virtual key or user is stronger than revoking a grant: it revokes the grant immediately and the identity check rejects the grant’s already-issued access token on its next
/mcprequest, instead of letting it live out its TTL. - Enabling
disable_vk_identity(require identity-provider login) cuts off all virtual-key–mode grants immediately — they are rejected at/mcpand denied on refresh — so those clients must re-authenticate as a user. Only applies inoauthmode with an identity provider configured. - Enabling
enforce_auth_on_inferenceblocks session-mode (anonymous) tokens at/mcp, but does not delete or invalidate their grants — the grants remain in the database and become valid again if enforcement is later disabled. To remove a session-mode grant permanently, revoke it on the OAuth Grants page.
Discovery Endpoints
When discovery is enabled (both or oauth), Bifrost serves the standard documents MCP clients fetch automatically:
| Endpoint | Purpose |
|---|---|
GET /.well-known/oauth-protected-resource | Protected resource metadata (RFC 9728) |
GET /.well-known/oauth-authorization-server | Authorization server metadata (RFC 8414) |
GET /.well-known/jwks.json | Public signing keys for JWT verification (RFC 7517) |
headers mode these endpoints return 404.
Troubleshooting
Discovery returns 404
Symptom: A client can’t discover the authorization server;.well-known endpoints return 404.
Cause: mcp_server_auth_mode is headers, so the OAuth surface is disabled.
Fix: Set the mode to both or oauth.
Header credential rejected on /mcp
Symptom: A virtual key or API key that worked before now gets a401.
Cause: mcp_server_auth_mode is oauth, which accepts JWTs only.
Fix: Use both to accept header credentials alongside OAuth.
Conflicting credentials on /mcp
Symptom:conflicting credentials: an OAuth token and a virtual key header were both provided.
Cause: The client completed an OAuth flow but is also configured with a VK header, so both arrive on one request. Claude Code does this in both mode when the VK is set under x-bf-vk / X-Api-Key instead of Authorization.
Fix: Send only one credential — remove the VK header, or configure it as Authorization: Bearer <vk> so the client uses header auth and skips OAuth.
Client keeps re-opening the browser
Symptom: The consent flow runs on every connection. Cause: The client isn’t persisting its tokens, or its refresh token was revoked. Fix: Confirm the client stores its credentials; check the OAuth Grants page to see whether the grant was revoked.Token rejected after issuer change
Symptom: Previously issued tokens fail validation after changingissuer_url.
Cause: The iss claim in existing tokens no longer matches the configured issuer.
Fix: Clients reconnect to obtain tokens with the new issuer; set a stable issuer_url up front in multi-host deployments.
Next Steps
- Bifrost as an MCP Gateway — Expose aggregated tools to external MCP clients.
- MCP Sessions — Inspect and manage per-user credentials for upstream servers.
- Virtual Keys — Govern budgets, rate limits, and tool scope for the identities behind grants.

