Authenticate MCP clients with OAuth 2.1
Use OAuth when an MCP client should act as an identified user instead of sharing a workspace API key. The DVARA MCP Gateway validates the access token, fixes the workspace boundary, enforces MCP scopes, and records the caller in the governance trail.
Not included in DVARA Open Source.
OAuth is off by default. When it is off, MCP calls require a DVARA workspace
API key. Turning it on accepts OAuth access tokens alongside API keys by
default; set api-keys-enabled: false only after every MCP client has moved.
Configure one authorization server
This example binds every accepted token from the issuer to one workspace. Use the claim-based form in the next section when one issuer serves several workspaces.
dvara:
mcp-gateway:
oauth:
enabled: true
resource: https://gateway.internal.example.com/mcp
authorization-servers:
- issuer: https://login.internal.example.com
workspace: claims-production
scopes-supported:
- mcp:read
- mcp:call
enforce-scopes: true
api-keys-enabled: true
require-pkce: true
resource is the MCP Gateway's canonical URI and must appear in the token's
aud claim. issuer must match the token's iss claim exactly. When no
explicit key-set URI is configured, DVARA discovers the issuer metadata and
requires advertised PKCE support with S256 by default.
Restart the Gateway after changing these process settings.
Map tokens to workspaces and operations
For a shared issuer, omit workspace and put the workspace ID in the token's
dvara_workspace claim. Change workspace-claim if your issuer uses a
different claim name. A token with neither an issuer binding nor the configured
workspace claim is refused.
With enforce-scopes: true, which is the default, clients need:
| Scope | Operations |
|---|---|
mcp:read | Initialize the session and list tools |
mcp:call | Call a tool |
An OAuth token with no scopes is not unrestricted. This differs from an unscoped DVARA API key.
Verify discovery and a token
After restart, fetch the unauthenticated protected-resource metadata:
curl --silent \
https://gateway.internal.example.com/.well-known/oauth-protected-resource/mcp
The response names the resource, authorization server, and supported scopes:
{
"resource": "https://gateway.internal.example.com/mcp",
"authorization_servers": ["https://login.internal.example.com"],
"scopes_supported": ["mcp:read", "mcp:call"],
"bearer_methods_supported": ["header"]
}
Then call tools/list with an access token:
curl --silent https://gateway.internal.example.com/mcp \
--header 'Authorization: Bearer <oauth-access-token>' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json, text/event-stream' \
--data '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}'
Open Governance → Audit and inspect the resulting MCP event. OAuth calls
record auth_method: oauth, the issuer, and the token subject; DVARA never
records the token itself.
Diagnose rejected clients
An invalid or expired token returns 401 with a WWW-Authenticate challenge
that points to the protected-resource metadata. A valid token without the
operation's scope returns 403 with error="insufficient_scope" and the
required scope.
Use clients.allowed to restrict callers to known client IDs. An empty list
accepts any client for which the configured issuer issued a valid token. For a
gradual move away from Dynamic Client Registration, set
clients.require-metadata-document: true to refuse clients that are neither
allow-listed nor represented by a Client ID Metadata Document. The default is
false.
Never pass the caller's access token through to an upstream MCP server. If an upstream uses delegated authorization, send a separate upstream token as described in Govern tool calls with the MCP Gateway.