> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ezforge.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Authentication

> How OAuth 2.1 with PKCE protects your MCP servers

Every MCP server deployed on ezForge is protected by **OAuth 2.1 with mandatory PKCE** (Proof Key for Code Exchange). This ensures that only authorized clients — AI assistants, agents, or applications — can call your server's tools.

For background on how ezForge's auth approach fits into the broader landscape of agentic auth patterns and how it compares to alternative implementation stacks, see [Auth for AI Agents and MCP Servers: a landscape guide](/concepts/agentic-auth-landscape).

## Client registration: CIMD vs DCR

ezForge supports two registration paths, matching the MCP Authorization Spec (2025-11 revision):

| Path                                           | Spec level             | How it works                                                                                   |
| ---------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------- |
| **CIMD** (Client ID Metadata Document)         | **should** — preferred | Client sets `client_id` to its own metadata URL; the AS fetches and registers it automatically |
| **DCR** (RFC 7591 Dynamic Client Registration) | may — fallback         | Client POSTs metadata to `/api/v1/servers/:id/clients`; explicit call required                 |

### CIMD registration (preferred)

CIMD is the preferred path for clients that can host a metadata document at a stable HTTPS URL (Claude Desktop, Gemini ADK, Cursor, etc.).

The client hosts a JSON document at a URL it controls, e.g.:

```
GET https://my-agent.example.com/.well-known/oauth-client-metadata
Content-Type: application/json
```

```json theme={null}
{
  "client_id": "https://my-agent.example.com/.well-known/oauth-client-metadata",
  "client_name": "My MCP Agent",
  "redirect_uris": ["https://my-agent.example.com/callback"],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "scope": "mcp:read mcp:write",
  "token_endpoint_auth_method": "none"
}
```

To begin the PKCE flow, the client uses its metadata URL as `client_id`:

```
GET /api/v1/oauth/authorize
  ?client_id=https://my-agent.example.com/.well-known/oauth-client-metadata
  &code_challenge=<S256>
  &code_challenge_method=S256
  &redirect_uri=https://my-agent.example.com/callback
  &scope=mcp:read mcp:write
  &resource=https://my-server.mcp.ezforge.ai
  &state=<random>
```

ezForge detects the URL-shaped `client_id`, fetches the metadata document, validates it, and registers the client automatically. No separate registration step is required.

The metadata cache is refreshed every 24 hours using ETag-based conditional requests (RFC 7232). Metadata fetch failures degrade safely — the authorization request is rejected with a clear error and no token is issued.

You can also pre-register a CIMD client explicitly (e.g. to validate the document ahead of the auth flow):

```bash theme={null}
curl -X POST https://app.ezforge.ai/api/v1/servers/{serverId}/clients \
  -H "Authorization: Bearer <session-token>" \
  -H "Content-Type: application/json" \
  -d '{ "clientMetadataUrl": "https://my-agent.example.com/.well-known/oauth-client-metadata" }'
```

### DCR registration (fallback)

DCR (RFC 7591) remains fully supported for clients that cannot host a metadata URL. See [Dynamic Client Registration](#ezforge_managed-mode) below.

## Why OAuth 2.1?

The [Model Context Protocol specification](https://modelcontextprotocol.io/docs) recommends OAuth 2.1 as the standard auth mechanism for HTTP-based MCP servers. It provides:

* **Short-lived access tokens** that limit the impact of token leakage
* **Refresh tokens** for long-lived sessions without re-authentication
* **Fine-grained scopes** to control what clients can do
* **PKCE** to prevent authorization code interception attacks (mandatory — `plain` method not accepted)

## How it works

```
MCP Client (AI Assistant)
         │
         │  1. Discover auth metadata
         ▼
GET /.well-known/oauth-protected-resource
         │
         │  2. Redirect to authorization server
         ▼
app.ezforge.ai/oauth/authorize
    ?client_id=...
    &code_challenge=<S256-hash>
    &code_challenge_method=S256
    &redirect_uri=...
    &scope=mcp:execute
         │
         │  3. User approves (or auto-approved for trusted clients)
         │  4. Auth code returned to redirect_uri
         ▼
POST app.ezforge.ai/oauth/token  ← in development
    grant_type=authorization_code
    &code=...
    &code_verifier=<original-random>
    &resource=https://my-server.mcp.ezforge.ai
         │
         │  5. Access token issued (bound to server URI)
         ▼
GET /sse
    Authorization: Bearer <access-token>
```

<Warning>
  **Token exchange is in active development.** The flow above reflects the designed OAuth architecture. `POST /api/v1/oauth/token` (step 4 — authorization code → access token) is not yet available. `GET /api/v1/oauth/authorize` (steps 2–3) is implemented and live.

  Customers requiring end-to-end OAuth today should use [BYOA mode](#byoa-mode-bring-your-own-auth).
</Warning>

## Resource Indicators (RFC 8707)

Access tokens are **bound to a specific server URI** using RFC 8707 Resource Indicators. A token issued for `my-server.mcp.ezforge.ai` cannot be used to call any other server. This prevents token replay attacks across servers.

## MCP scopes

| Scope            | Description                             |
| ---------------- | --------------------------------------- |
| `mcp:read`       | List available tools on the server      |
| `mcp:write`      | Call tools that modify state            |
| `mcp:execute`    | Execute any tool call (most permissive) |
| `offline_access` | Request long-lived refresh tokens       |

## ezforge\_managed mode

When a server uses `ezforge_managed` auth (the default), ezForge acts as the OAuth authorization server:

* ezForge registers the MCP client
* Handles the authorization flow
* Issues and validates tokens
* No configuration required from you

## BYOA mode (Bring Your Own Auth)

If you already run an OAuth 2.1 authorization server, you can configure your ezForge server to accept tokens from it:

```bash theme={null}
ezforge servers update my-server \
  --auth-mode byoa \
  --byoa-issuer https://auth.example.com \
  --byoa-jwks-uri https://auth.example.com/.well-known/jwks.json \
  --byoa-authorization-endpoint https://auth.example.com/oauth/authorize \
  --byoa-token-endpoint https://auth.example.com/oauth/token
```

**ezForge uses a proxy/passthrough model for BYOA token validation.**

When a request arrives with an `Authorization: Bearer <token>` header, ezForge performs a lightweight structural check: it confirms that a syntactically valid Bearer token is present (RFC 6750 §2.1). If the header is absent or malformed, the request is rejected immediately.

If the structural check passes, ezForge forwards the token as-is to your MCP server via the auth header proxy (spec §3.3 auth header proxy). **Your MCP server — or the external IdP configured in `byoaConfig` — is responsible for full JWT verification:** signature validation, `iss` claim check, `aud` claim check, and expiry.

<Note>
  Customers using BYOA mode are responsible for JWT verification at their own MCP server endpoint. ezForge does not fetch your JWKS, verify JWT signatures, or inspect claims on your behalf.
</Note>

The `--byoa-jwks-uri` value is forwarded verbatim into the RFC 9728 Protected Resource Metadata response so MCP clients can discover your IdP's JWKS endpoint; ezForge itself does not fetch or validate against it.

## Choosing between `ezforge_managed` and BYOA

Both modes deliver the same MCP-spec compliance (OAuth 2.1, PKCE, RFC 8707 resource binding, RFC 9728 protected resource metadata). The choice is about who owns identity issuance.

| Choose `ezforge_managed` if…                                                          | Choose BYOA if…                                                                                                   |
| ------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| You want a working spec-compliant MCP server in minutes, with zero auth configuration | You already operate an OAuth 2.1 authorization server you want to keep                                            |
| You don't yet have an enterprise identity provider                                    | You have existing SSO/SCIM contracts (WorkOS, Auth0, Stytch/Twilio, etc.) you need to honor                       |
| You want ezForge to handle user accounts, token issuance, and JWKS rotation           | You want auth audit logs and account lifecycle inside your own IdP                                                |
| You're an indie developer, SMB customer, or running a single-tenant deployment        | You have residency, isolation, or compliance constraints that require keeping identity in your own infrastructure |

Both modes use the same `ezforge servers update --auth-mode {ezforge_managed,byoa}` command — see the [BYOA mode](#byoa-mode-bring-your-own-auth) section above for the BYOA-specific flags.

## Protected Resource Metadata

All ezForge-hosted servers expose the RFC 9728 standard endpoint:

```
GET https://{slug}.mcp.ezforge.ai/.well-known/oauth-protected-resource
```

```json theme={null}
{
  "resource": "https://my-server.mcp.ezforge.ai",
  "authorization_servers": ["https://app.ezforge.ai"],
  "scopes_supported": ["mcp:read", "mcp:write", "mcp:execute", "offline_access"],
  "bearer_methods_supported": ["header"]
}
```

This allows MCP clients to **automatically discover** how to authenticate — no manual configuration needed.

## Token lifetimes

| Token              | Lifetime   | Notes                                       |
| ------------------ | ---------- | ------------------------------------------- |
| Authorization code | 10 minutes | Single-use; consumed on token exchange      |
| Access token       | 15 minutes | Short-lived; limits blast radius of leakage |
| Refresh token      | 30 days    | Rotated on each use                         |

## Security recommendations

* Request only the scopes your application needs
* Use `offline_access` only when a long-lived session is necessary
* Treat access tokens as secrets — don't log them or include them in URLs
* Rotate refresh tokens regularly; ezForge rotates them automatically on use
