You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Clients can use an HTTPS URL as their client_id; the server fetches and
validates the metadata document from that URL per
draft-ietf-oauth-client-id-metadata-document-00. Opt-in via the
clientIdMetadataDocuments option. Documents are cached through the
OAuthServerModel (saveClientIdMetadataDocument /
getClientIdMetadataDocument) respecting Cache-Control headers.
The draft MCP Authorization spec recommends CIMD and deprecates Dynamic
Client Registration in its favor.
-**MCP Authorization Spec compliant**: Aligns with the [MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)
-**MCP Authorization Spec compliant**: Aligns with the [MCP Authorization Spec](https://modelcontextprotocol.io/specification/draft/basic/authorization)
33
+
-[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) with mandatory PKCE (`S256` only, [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636))
34
+
-[Client ID Metadata Documents](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00) - opt-in, see [CIMD](#client-id-metadata-documents-cimd)
- Resource Indicators [(RFC 8707)](https://datatracker.ietf.org/doc/html/rfc8707) with token audience validation
37
+
- Dynamic Client Registration [(RFC 7591)](https://datatracker.ietf.org/doc/html/rfc7591) - deprecated by the MCP spec in favor of [CIMD](#client-id-metadata-documents-cimd), kept for backwards compatibility
- Authorization Server Issuer Identification [(RFC 9207)](https://datatracker.ietf.org/doc/html/rfc9207)
38
42
- Loopback redirect URIs with any port for native apps [(RFC 8252 §7.3)](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3)
39
-
-**Grant types**: Configurable via `grantTypes`—`authorization_code`, `refresh_token`, [`client_credentials`](#oauth-client-credentials-machine-to-machine), and [device authorization](https://datatracker.ietf.org/doc/html/rfc8628) (`urn:ietf:params:oauth:grant-type:device_code`)
43
+
-**Grant types**: Configurable via `grantTypes`-`authorization_code`, `refresh_token`, [`client_credentials`](#oauth-client-credentials-machine-to-machine), and [device authorization](https://datatracker.ietf.org/doc/html/rfc8628) (`urn:ietf:params:oauth:grant-type:device_code`, RFC 8628)
40
44
-**Compatibility**: Works with MCP clients that omit a `resource` indicator [(RFC 8707)](https://datatracker.ietf.org/doc/html/rfc8707) or requested scopes when needed (`strictResource`)
41
45
-**Flexible storage**: In-memory model for development (`MemoryOAuthServerModel`) or your own `OAuthServerModel` for production
-`private_key_jwt` client authentication for CIMD clients - CIMD clients are treated as public clients (`token_endpoint_auth_method: 'none'`).
51
+
-[OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) - RFC 8414 metadata satisfies the MCP spec's discovery requirement on its own.
44
52
45
53
## OAuth client credentials (machine-to-machine)
46
54
@@ -65,7 +73,7 @@ const oauthServer = new OAuthServer({
65
73
66
74
`POST` to the token endpoint with `grant_type=client_credentials` and authenticate the client (for example `client_id` / `client_secret` per [RFC 6749 §4.4](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4)). MCP clients using `@modelcontextprotocol/client` can use `ClientCredentialsProvider` as described in the extension docs above.
67
75
68
-
Tokens minted for this grant typically have **no `userId`** on `AuthInfo`— authorize by `clientId` and scopes where appropriate.
76
+
Tokens minted for this grant typically have **no `userId`** on `AuthInfo`- authorize by `clientId` and scopes where appropriate.
69
77
70
78
## Device authorization (RFC 8628)
71
79
@@ -87,14 +95,48 @@ const oauthServer = new OAuthServer({
87
95
});
88
96
```
89
97
90
-
3. Implement the device-related methods on `OAuthServerModel` (`saveDeviceAuthorization`, `getDeviceAuthorizationByDeviceCode`, `getDeviceAuthorizationByUserCode`, `deleteDeviceAuthorization`) — see `MemoryOAuthServerModel` for a reference.
98
+
3. Implement the device-related methods on `OAuthServerModel` (`saveDeviceAuthorization`, `getDeviceAuthorizationByDeviceCode`, `getDeviceAuthorizationByUserCode`, `deleteDeviceAuthorization`) - see `MemoryOAuthServerModel` for a reference.
91
99
92
100
The auth router exposes **`POST /device`** (under your AS base path) when the device grant and `deviceAuthorizationUrl` are configured. Metadata lists `device_authorization_endpoint` accordingly.
93
101
94
102
**Approving or denying a login**
95
103
96
104
Wire **`approveDeviceAuthorizationHandler`** and **`denyDeviceAuthorizationHandler`** on routes you choose; they accept `user_code` (and resolve the authenticated user via `getUser`) so the user can approve or reject the device login out-of-band.
97
105
106
+
## Client ID Metadata Documents (CIMD)
107
+
108
+
[Client ID Metadata Documents](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00) let clients use an HTTPS URL as their `client_id`. The authorization server fetches a JSON metadata document from that URL (`client_id`, `client_name`, `redirect_uris`, ...) instead of requiring registration. The [draft MCP Authorization spec](https://modelcontextprotocol.io/specification/draft/basic/authorization/client-registration#client-id-metadata-documents) recommends CIMD and deprecates Dynamic Client Registration in its favor.
109
+
110
+
Support is **opt-in** because it makes the server issue outbound HTTPS requests to client-supplied URLs:
111
+
112
+
```ts
113
+
const oauthServer =newOAuthServer({
114
+
// enable with defaults
115
+
clientIdMetadataDocuments: true,
116
+
117
+
// or configure it
118
+
clientIdMetadataDocuments: {
119
+
// Trust policy: reject metadata URLs outside your allowlist
fetch: myCustomFetch, // defaults to the Node built-in fetch
125
+
},
126
+
// ...
127
+
});
128
+
```
129
+
130
+
When enabled, the server metadata advertises `client_id_metadata_document_supported: true` and `OAuthServer.getClient` resolves URL-formatted client ids by fetching and validating the document: the document's `client_id` must equal the URL exactly, and `client_name` and at least one `redirect_uris` entry are required.
131
+
132
+
Fetched documents are cached through your `OAuthServerModel` (`saveClientIdMetadataDocument` / `getClientIdMetadataDocument`, implemented by `MemoryOAuthServerModel` out of the box) respecting `Cache-Control` headers, so multiple server instances sharing a model share the cache. Enabling CIMD on a custom model requires implementing both methods.
133
+
134
+
**Security notes**
135
+
136
+
- CIMD clients are public clients - documents demanding any `token_endpoint_auth_method` other than `none` are rejected (`private_key_jwt` is not supported).
137
+
- Non-HTTPS URLs, loopback/IP-literal hosts, redirects, and documents over 10 KB are always rejected. These checks do not cover DNS rebinding or internal hostnames - use `validateClientIdUrl` (or network egress filtering) if the server can reach internal services.
138
+
- CIMD cannot prevent localhost redirect URI impersonation by itself; consent screens should display the redirect URI hostname to the user.
139
+
98
140
## Quick Start
99
141
100
142
A working MCP OAuth example with a memory-backed authorization server lives in [`./example`](example).
@@ -146,7 +188,8 @@ const oauthServer = new OAuthServer({
146
188
-`strictResource`: (optional) Validate the RFC 8707 `resource` parameter on authorize requests. Default: `true`.
147
189
-`modifyAuthorizationRedirectUrl`: (optional) Mutate the consent redirect URL (e.g. add client display hints as query parameters).
148
190
-`errorHandler`: (optional) Hook for logging or handling errors inside OAuth flows.
149
-
-`dynamicClientRegistration`: (optional) Enable RFC 7591 `/register`. Default: `true`. Construction fails if enabled and `model.registerClient` is missing.
191
+
-`dynamicClientRegistration`: (optional) Enable RFC 7591 `/register`. Default: `true`. Construction fails if enabled and `model.registerClient` is missing. Note: the MCP spec deprecates Dynamic Client Registration in favor of [CIMD](#client-id-metadata-documents-cimd); keep it enabled for backwards compatibility with clients that do not support CIMD.
192
+
-`clientIdMetadataDocuments`: (optional) Enable [Client ID Metadata Documents](#client-id-metadata-documents-cimd) - pass `true` or a `ClientIdMetadataDocumentOptions` object. Default: `false`.
-`deviceAuthorizationUrl`: (optional) Page URL where the user enters the user code (RFC 8628). Required together with the device grant on `grantTypes`.
152
195
-`deviceAuthorizationLifetime`: (optional) Device code lifetime in seconds. Default: `900`.
@@ -184,6 +227,7 @@ export class PostgresModel implements OAuthServerModel {
184
227
-`registerClient`: (required if `dynamicClientRegistration` is true) Persist dynamic registration.
185
228
- Authorization code grant: `saveAuthorizationCode`, `getAuthorizationCode`, `revokeAuthorizationCode` when `authorization_code` is enabled.
186
229
- Device grant: `saveDeviceAuthorization`, `getDeviceAuthorizationByDeviceCode`, `getDeviceAuthorizationByUserCode`, `deleteDeviceAuthorization` when the device grant is enabled.
230
+
- CIMD: `saveClientIdMetadataDocument`, `getClientIdMetadataDocument` when [`clientIdMetadataDocuments`](#client-id-metadata-documents-cimd) is enabled.
0 commit comments