arcade-mcp/libs/arcade-mcp-server/arcade_mcp_server/resource_server
Eric Gustin 98fd13c4ed
Front-Door Auth (#696)
# Valuable references for the reviewer:
- Docs PR: https://github.com/ArcadeAI/docs/pull/583
- Implements Phase 1 of the following planning doc:
https://linear.app/arcadedev/project/arcade-mcp-supports-mcp-auth-front-door-auth-7cbaa20cb054/overview


https://github.com/user-attachments/assets/79ad43fd-f5e8-4793-a1dd-18b35acefdc3

# PR Description
Adds OAuth 2.1 Resource Server authentication to arcade-mcp-server,
enabling HTTP MCP servers to validate Bearer tokens on every request.
This unlocks tool-level authorization and secrets support for HTTP
servers.

- Multiple authorization server support
- Granular token validation options (verify_exp, verify_iat, verify_iss)
- Environment variable configuration
- OAuth discovery metadata endpoint
(/.well-known/oauth-protected-resource)
- Extracts sub claim from token as context.user_id
- Lifts transport restrictions for tools requiring auth/secrets on HTTP
when protected

```python
from arcade_mcp_server import MCPApp
from arcade_mcp_server.resource_server import ResourceServerAuth, AuthorizationServerEntry

resource_server_auth = ResourceServerAuth(
    canonical_url="http://127.0.0.1:8000/mcp",
    authorization_servers=[
        AuthorizationServerEntry(
            authorization_server_url="https://auth.example.com",
            issuer="https://auth.example.com",
            jwks_uri="https://auth.example.com/jwks",
        )
    ],
)

app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth)
```

# Testing
Beyond the comprehensive unit tests, I also manually tested end-to-end
with WorkOS Authkit (DCR) and KeyCloak (non-DCR).

# Future Work
- CIMD support
- An `ArcadeResourceServer` to make adding front-door auth super easy
when using Arcade's Auth Server



<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds OAuth 2.1 front-door auth (JWKS validation + OAuth discovery) and
propagates user identity to tools, enabling auth/secret-requiring tools
over HTTP.
> 
> - **Authentication (Front-Door OAuth 2.1)**
> - New `resource_server` module with `ResourceServerAuth`
(multi-authorization-server, metadata) and `JWKSTokenValidator`
(JWKS-based JWT validation) plus granular validation options.
> - ASGI `ResourceServerMiddleware` validates Bearer tokens on every
HTTP request and injects `resource_owner`.
> - OAuth discovery endpoint via FastAPI router at
`/.well-known/oauth-protected-resource[/<path>]`.
> - **Integration**
> - `MCPApp`/`worker` accept `auth`/`resource_server_validator`, mount
middleware, expose discovery; logs accepted auth servers.
> - HTTP transport (`http_streamable`) carries `SessionMessage` with
`resource_owner` from request → session.
> - `Context`/`Session`/`Server` plumb `resource_owner`; `Server`
selects `user_id` preferring token `sub`.
> - **Behavior Changes**
> - HTTP transport restriction lifted for tools requiring
`authorization`/`secrets` when request is authenticated; otherwise
blocked with actionable error.
> - **Configuration**
> - Env-var based auth config via `MCP_RESOURCE_SERVER_*` in
`MCPSettings.ResourceServerSettings`; `.env` auto-load.
> - **Telemetry**
>   - Usage tracking records `resource_server_type` on server start.
> - **Examples**
> - New `examples/mcp_servers/authorization` sample server (HTTP auth,
secrets, Reddit tool) with Docker setup.
> - **Tests**
> - Extensive unit tests for validators, middleware, env config,
multi-AS, transport rules, and app integration.
> - **Version**
> - Bump `arcade-mcp-server` to `1.12.0`; minor docstring tweak in
`__init__.py`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d1116cdcafb0c7cb8f91e66682eb1fbae380da31. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->





Resolves TOO-152
2025-12-11 12:51:20 -08:00
..
validators Front-Door Auth (#696) 2025-12-11 12:51:20 -08:00
__init__.py Front-Door Auth (#696) 2025-12-11 12:51:20 -08:00
base.py Front-Door Auth (#696) 2025-12-11 12:51:20 -08:00
middleware.py Front-Door Auth (#696) 2025-12-11 12:51:20 -08:00
README.md Front-Door Auth (#696) 2025-12-11 12:51:20 -08:00

MCP Resource Server Authentication

OAuth 2.1-compliant Resource Server authentication for securing HTTP-based MCP servers.

Overview

The MCP server acts as an OAuth 2.1 Resource Server, validating Bearer tokens on every HTTP request before processing MCP protocol messages. This enables:

  1. Secure HTTP Transport - Protect your MCP server with OAuth 2.1
  2. Tool-Level Authorization - Enable tools requiring end-user OAuth on HTTP transport
  3. OAuth Discovery - MCP clients automatically discover authentication requirements via OAuth Protected Resource Metadata (RFC 9728)
  4. User Context - Tools receive authenticated resource owner identity from the Authorization Server

MCP servers can accept tokens from one or more authorization servers. Accepting tokens from multiple authorization servers supports scenarios like regional endpoints, multiple identity providers, or migrating between auth systems.

Note: The MCP server (Resource Server) doesn't need to know how MCP clients are registered with the Authorization Server (for example, Dynamic Client Registration, static client secrets, etc.) - that's the authorization server's concern. The MCP server simply validates tokens and advertises the AS URLs.

Environment Variable Configuration

ResourceServerAuth supports environment variable configuration for production deployments. This is the recommended approach for production.

Note: JWKSTokenValidator does not support environment variables and requires explicit programmatic parameters to its initializer

Supported Environment Variables

Environment Variable Type Description Required
MCP_RESOURCE_SERVER_CANONICAL_URL string MCP server canonical URL Yes
MCP_RESOURCE_SERVER_AUTHORIZATION_SERVERS JSON array Authorization server entries Yes

The MCP_RESOURCE_SERVER_AUTHORIZATION_SERVERS must be a JSON array of entry objects. Each object should include:

  • authorization_server_url: Authorization server URL
  • issuer: Expected token issuer
  • jwks_uri: JWKS endpoint URL
  • algorithm: (Optional) JWT algorithm, defaults to RS256
  • expected_audiences: (Optional) list of expected audience claim values. If not provided, defaults to the canonical_url. Use this when your auth server returns a different aud claim (e.g., client_id).
  • validation_options: (Optional) dict with optional verify_exp, verify_iat, verify_iss, verify_nbf, and leeway (int, seconds). All verify flags default to True.

Precedence Rules

Explicit parameters take precedence over environment variables:

from arcade_mcp_server import MCPApp
from arcade_mcp_server.resource_server import (
    AuthorizationServerEntry,
    ResourceServerAuth,
)

# Explicit parameters override env vars (if both are provided)
resource_server_auth = ResourceServerAuth(
    canonical_url="http://127.0.0.1:8000/mcp",  # used even if env var is set
    authorization_servers=[  # used even if env var is set
        AuthorizationServerEntry(
            authorization_server_url="https://your-workos.authkit.app",
            issuer="https://your-workos.authkit.app",
            jwks_uri="https://your-workos.authkit.app/oauth2/jwks",
            algorithm="RS256",
            # Override expected aud if auth server returns different audience (e.g., client_id)
            expected_audiences=["my-authkit-client-id"],
        )
    ],
)
app = MCPApp(name="Protected", auth=resource_server_auth)

# If no parameters provided, env vars are used as fallback
resource_server_auth = ResourceServerAuth()  # Uses MCP_RESOURCE_SERVER_* env vars

Example .env File

Single Authorization Server

# Resource Server Configuration
MCP_RESOURCE_SERVER_CANONICAL_URL=https://mcp.example.com/mcp
MCP_RESOURCE_SERVER_AUTHORIZATION_SERVERS='[
  {
    "authorization_server_url": "https://auth.example.com",
    "issuer": "https://auth.example.com",
    "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
    "algorithm": "RS256"
  }
]'

Single Authorization Server (Custom Audience)

When your auth server returns a different aud claim (e.g., client_id instead of canonical URL):

MCP_RESOURCE_SERVER_CANONICAL_URL=https://mcp.example.com/mcp
MCP_RESOURCE_SERVER_AUTHORIZATION_SERVERS='[
  {
    "authorization_server_url": "https://auth.example.com",
    "issuer": "https://auth.example.com",
    "jwks_uri": "https://auth.example.com/.well-known/jwks.json",
    "algorithm": "RS256",
    "expected_audiences": ["my-client-id"]
  }
]'

Multiple Authorization Servers (Shared Keys)

# Regional endpoints with shared keys
MCP_RESOURCE_SERVER_CANONICAL_URL=https://mcp.example.com/mcp
MCP_RESOURCE_SERVER_AUTHORIZATION_SERVERS='[
  {
    "authorization_server_url": "https://auth-us.example.com",
    "issuer": "https://auth.example.com",
    "jwks_uri": "https://auth.example.com/.well-known/jwks.json"
  },
  {
    "authorization_server_url": "https://auth-eu.example.com",
    "issuer": "https://auth.example.com",
    "jwks_uri": "https://auth.example.com/.well-known/jwks.json"
  }
]'

Multiple Authorization Servers (Different Keys)

# Multi-IdP configuration with custom audiences
MCP_RESOURCE_SERVER_CANONICAL_URL=https://mcp.example.com/mcp
MCP_RESOURCE_SERVER_AUTHORIZATION_SERVERS='[
  {
    "authorization_server_url": "https://workos.authkit.app",
    "issuer": "https://workos.authkit.app",
    "jwks_uri": "https://workos.authkit.app/oauth2/jwks",
    "expected_audiences": ["my-workos-client-id"]
  },
  {
    "authorization_server_url": "http://localhost:8080/realms/mcp-test",
    "issuer": "http://localhost:8080/realms/mcp-test",
    "jwks_uri": "http://localhost:8080/realms/mcp-test/protocol/openid-connect/certs",
    "expected_audiences": ["my-keycloak-client-id"]
  }
]'

How It Works

  1. Resource Server validates tokens - Extracts user identity from validated token's sub claim
  2. User ID flows to ToolContext - Used for tool-level OAuth via Arcade platform
  3. Transport restriction lifted - HTTP is now safe for tools requiring auth/secrets
  4. Separate authorization layers - Resource Server auth != tool OAuth (but building a protected server enables tool authorization)

Vendor-Specific Implementations

The ResourceServerAuth class is designed to be subclassed for vendor-specific implementations:

# Your vendor-specific implementations
class ArcadeResourceServerAuth(ResourceServerAuth): ...
class WorkOSResourceServerAuth(ResourceServerAuth): ...
class Auth0ResourceServerAuth(ResourceServerAuth): ...
class DescopeResourceServerAuth(ResourceServerAuth): ...