Skip to content

RFC: support CIMD inbound at /authorize #115

@BorisTyshkevich

Description

@BorisTyshkevich

Summary

altinity-mcp should replace Dynamic Client Registration (DCR) with OAuth Client ID Metadata Documents (CIMD) for MCP OAuth client metadata.

DCR is not widely used in current altinity-mcp deployments. It has mainly been exercised on a small set of stage/dev servers for interoperability testing with ChatGPT and Claude. Current testing shows both ChatGPT and Claude support CIMD. The corporate Auth0 tenant also has DCR disabled, so relying on DCR does not match the production authorization environment.

The proposed direction is:

  • support CIMD as the only dynamic MCP OAuth client metadata mechanism;
  • advertise CIMD support in authorization server metadata;
  • remove DCR support immediately;
  • do not advertise registration_endpoint;
  • reject calls to any remaining DCR registration route if the route still exists during cleanup;
  • define strict URL, metadata, cache, trust, redirect, logging, and SSRF requirements for CIMD;
  • make the authorization-code replay model explicit for HA deployments without shared storage.

Background

MCP clients and MCP authorization servers need a way to exchange OAuth client metadata, especially client_id, client_name, redirect_uris, and token endpoint authentication method, when the client and server have no prior relationship.

Historically, altinity-mcp supported this through DCR. With DCR, the client calls a registration endpoint, submits metadata, and receives a server-issued client identifier. This requires the MCP server to expose and maintain a registration surface.

CIMD uses a different model. The OAuth client_id itself is an HTTPS URL. That URL points to a JSON metadata document owned by the client. The authorization server fetches the document, validates it, and uses it as the client registration metadata for the OAuth flow.

The MCP authorization specification lists CIMD as the preferred dynamic mechanism for clients and authorization servers. DCR is optional and included for backwards compatibility with earlier versions of the MCP authorization spec.

DCR vs CIMD

Area DCR CIMD
Client identity Server issues a client identifier after registration. Client identifier is a stable HTTPS URL controlled by the client.
Metadata delivery Client POSTs metadata to a registration endpoint. Authorization server fetches metadata from the client_id URL.
Server write surface Requires a registration endpoint. No registration endpoint required.
Client lifecycle Server may need to manage registered clients, expiration, cleanup, and abuse controls. Metadata is resolved on demand and cached.
HA / stateless operation More stateful unless registration data is encoded or shared. Fits stateless and multi-replica deployments, but authorization-code replay still needs an explicit model.
Production fit for Altinity Corporate Auth0 tenant has DCR disabled. Matches current MCP direction and tested ChatGPT/Claude behavior.
MCP direction Optional backwards-compatibility path. Preferred path for clients with no prior relationship.
Main risk Registration endpoint abuse and lifecycle complexity. SSRF risk from fetching attacker-controlled URLs.

Proposal

Use CIMD as the default and only dynamic mechanism for inbound MCP OAuth clients.

DCR should be deprecated and removed immediately. There should be no new compatibility flag for DCR unless a concrete production client requires it later.

This issue is intentionally written as requirements rather than implementation detail. The implementation can choose the internal resolver, cache, and code organization as long as it satisfies the requirements below.

Requirements

Authorization server metadata

The authorization server metadata should advertise CIMD support and the token endpoint authentication methods supported by altinity-mcp:

{
  "issuer": "https://mcp.example.com",
  "authorization_endpoint": "https://mcp.example.com/oauth/authorize",
  "token_endpoint": "https://mcp.example.com/oauth/token",
  "client_id_metadata_document_supported": true,
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "token_endpoint_auth_methods_supported": ["none"],
  "code_challenge_methods_supported": ["S256"]
}

Requirements:

  • none is required for public CIMD clients using PKCE.
  • Shared-secret token endpoint authentication methods must not be advertised for CIMD.
  • registration_endpoint must not be advertised.
  • client_id_metadata_document_supported: true must be advertised.
  • code_challenge_methods_supported must include S256.
  • Any remaining DCR registration route must return a clear unsupported/removed error until the route is deleted.

DCR removal

DCR should be removed in the same change or explicitly disabled with no supported configuration path.

Requirements:

  • Do not advertise DCR in OAuth authorization server metadata.
  • Do not mount /oauth/register as a working endpoint.
  • Do not issue new server-minted DCR client_id values.
  • Remove DCR documentation, except for a migration note saying CIMD replaced DCR.
  • Remove DCR tests, except negative tests that verify DCR is not exposed.
  • Remove DCR metrics after the route is deleted.
  • Remove or deprecate DCR-specific configuration such as custom registration-path settings.

CIMD client identifier URL validation

altinity-mcp should accept a client_id as a CIMD metadata URL only when all of the following are true:

  • the client_id parses as an absolute URL;
  • the scheme is exactly https;
  • the URL has a hostname;
  • the URL has a non-empty path component;
  • the URL has no fragment;
  • the URL has no username or password component;
  • the URL has no query string in v1;
  • the URL length is below a configured maximum, with a default maximum of 2048 characters;
  • the port is allowed by policy;
  • the hostname is allowed by policy;
  • the path contains no single-dot or double-dot path segments;
  • the path contains no encoded single-dot or double-dot path segments;
  • the path contains no normalization edge cases that would cause two different strings to identify the same effective path.

Dot-segment and normalization rules

The implementation must reject dot-segment variants before fetching the metadata document.

Reject examples include:

  • /./client.json
  • /../client.json
  • /a/./client.json
  • /a/../client.json
  • /%2e/client.json
  • /%2E/client.json
  • /%2e%2e/client.json
  • /%2E%2E/client.json
  • /%2e./client.json
  • /.%2e/client.json

Validation requirements:

  • Parse the URL using a standards-compliant URL parser.
  • Do not normalize the path and then accept the normalized result.
  • Split the raw path into path segments.
  • Strictly percent-decode each segment for validation.
  • Reject malformed percent encodings.
  • Reject any raw or decoded segment equal to . or ...
  • Reject encoded slash and encoded backslash in path segments, such as %2f, %2F, %5c, and %5C, to avoid segment-boundary ambiguity.
  • Reject paths where applying standard dot-segment removal would change the path.
  • Use the original exact client_id string for metadata client_id comparison and cache keying.

Query string policy

CIMD says client identifier URLs should not include a query string. For v1, altinity-mcp should reject query strings completely.

Requirements:

  • Reject any CIMD client_id URL with a non-empty query component.
  • Do not support query-string based metadata routing in v1.
  • Do not log full request URLs that could contain sensitive query strings.
  • OAuth redirect_uri values may contain query strings only when they are part of the registered redirect URI and are matched according to the redirect URI policy below.

Trust policy

CIMD allows unknown clients, but altinity-mcp still needs an explicit production trust policy.

Production default

Production default should be open internet for CIMD metadata hostnames after strict SSRF validation.

This preserves the main CIMD value: MCP clients can connect without prior registration. SSRF controls, metadata validation, token endpoint authentication, and redirect policies provide the baseline safety checks.

Optional hostname allowlist

Operators may configure an allowlist to restrict accepted CIMD client_id hostnames.

Suggested configuration shape:

ALTINITY_MCP_CIMD_ALLOWED_HOSTS=
ALTINITY_MCP_CIMD_ALLOWED_PORTS=443
ALTINITY_MCP_CIMD_TRUSTED_LOOPBACK_REDIRECT_HOSTS=
ALTINITY_MCP_CIMD_DEV_ALLOW_SPECIAL_USE_IPS=false

Behavior:

  • Empty ALTINITY_MCP_CIMD_ALLOWED_HOSTS means public internet hostnames are allowed after SSRF validation.
  • Non-empty ALTINITY_MCP_CIMD_ALLOWED_HOSTS means only matching hostnames are allowed.
  • Hostnames are normalized to lower-case ASCII using IDNA before comparison.
  • Exact host matches are allowed.
  • Wildcards are allowed only as the entire left-most label, for example *.example.com.
  • *.example.com matches a.example.com but not example.com and not a.b.example.com.
  • Wildcards must not be allowed for public suffixes such as *.com.
  • Partial wildcards such as *example.com, api.*.example.com, and example.* are rejected.
  • CIDR ranges are not accepted in hostname allowlists; IP filtering belongs to SSRF validation.

Allowed ports

Production default should allow only port 443 for CIMD metadata URLs.

Behavior:

  • URL with no explicit port is treated as port 443.
  • URL with explicit port 443 is allowed.
  • Any other port is rejected unless explicitly listed in ALTINITY_MCP_CIMD_ALLOWED_PORTS.
  • Stage/dev may add non-standard HTTPS ports explicitly.
  • HTTP is never allowed for CIMD metadata URLs.

Dev/stage overrides

Dev/stage may enable controlled exceptions, but they must be explicit.

Requirements:

  • Special-use IP ranges remain rejected by default.
  • Loopback/private/link-local CIMD metadata URLs are allowed only if ALTINITY_MCP_CIMD_DEV_ALLOW_SPECIAL_USE_IPS=true.
  • When dev special-use IPs are enabled, restrict them to local development deployments and log a startup warning.
  • The override must not be enabled by default in production images, Helm charts, or example production configs.

Failure mode

  • If hostname, port, trust, or SSRF validation fails, abort the authorization request.
  • Return an OAuth-compatible error to the client.
  • Log a redacted structured reason for operators.
  • Do not fall back to DCR.

Metadata fetch requirements

The metadata document is fetched only after URL, trust, and SSRF validation succeeds.

Requirements:

  • Use HTTP GET only.
  • Require HTTP 200 OK.
  • Do not automatically follow redirects.
  • Require JSON content.
  • Enforce a small body size limit, default 5 KiB.
  • Enforce a decompressed-size limit if compression is accepted.
  • Use a short timeout.
  • Send no cookies.
  • Send no authorization headers.
  • Send no client credentials.
  • Send no ambient headers from the inbound user request.
  • Do not use environment-configured HTTP proxies.

Metadata schema validation

The fetched metadata document must be treated as untrusted input.

Minimum required fields:

  • client_id
  • client_name
  • redirect_uris
  • token_endpoint_auth_method

Validation requirements:

  • The document must be a JSON object.
  • Duplicate JSON object keys should be rejected where the JSON parser supports detection.
  • Unknown fields may be ignored after total document size and field size limits are enforced.
  • client_id must be a string.
  • client_id must exactly equal the original requested client_id string using simple string comparison.
  • client_name must be a non-empty string.
  • client_name must have a bounded length, suggested maximum 128 characters.
  • redirect_uris must be a non-empty array of strings.
  • redirect_uris must have a bounded length, suggested maximum 20 entries.
  • Each redirect URI string must have a bounded length, suggested maximum 2048 characters.
  • Duplicate redirect URIs should be rejected.
  • grant_types, if present, must include authorization_code and must not require unsupported grant types.
  • response_types, if present, must include code and must not require unsupported response types.
  • token_endpoint_auth_method must be explicitly present.
  • token_endpoint_auth_method must be none.
  • Omitted token_endpoint_auth_method is rejected because RFC 7591 defines the omitted default as client_secret_basic.
  • Shared-secret methods are rejected for CIMD, including client_secret_basic, client_secret_post, and client_secret_jwt.
  • client_secret and client_secret_expires_at are rejected in CIMD metadata.
  • Client assertions and private_key_jwt are not supported in v1 and must be rejected if requested.
  • Unsupported fields must not influence security decisions.

Redirect URI policy

Redirect URI validation must be strict.

Requirements:

  • Non-loopback redirect URIs must use https.
  • Non-loopback redirect URI matching must be exact string matching against the validated metadata document.
  • http redirect URIs are allowed only for loopback redirect hosts.
  • Loopback redirect hosts are limited to localhost, 127.0.0.1, and [::1].
  • Loopback redirects are accepted in production only when the CIMD client_id hostname is trusted for loopback redirects.
  • Trusted loopback redirect hostnames are configured separately from the general CIMD hostname allowlist.
  • If loopback redirects are accepted, the consent or approval surface must clearly display the redirect URI hostname.
  • If all registered redirect URIs are loopback redirects, show an additional warning where a user-facing consent surface exists.
  • If there is no user-facing consent or approval surface, reject loopback redirects unless the client ID hostname is explicitly trusted.
  • Query strings in redirect URIs are allowed only when registered and matched according to the exact redirect policy.

Loopback redirect port handling

For compatibility with native clients, loopback redirect port handling may be port-agnostic only under a narrow policy.

Requirements:

  • For non-loopback redirect URIs, the port must match exactly.

  • For loopback redirect URIs, the port may vary only if:

    • the registered redirect URI is loopback;
    • the scheme, host, path, and query match policy;
    • the client ID hostname is trusted for loopback redirects;
    • the implementation documents the port matching behavior.
  • Do not support wildcard redirect paths.

  • Do not support wildcard redirect hostnames.

Client trust and consent display

Open-internet CIMD means the authorization server may see previously unknown clients. The user-facing approval or consent surface must therefore avoid presenting dynamic clients as implicitly trusted.

Requirements:

  • Display the CIMD client_id hostname or full origin to the user where an approval surface exists.
  • Display the selected redirect_uri hostname where an approval surface exists.
  • Do not fetch or inline remote logo_uri, client_uri, tos_uri, or similar optional display assets in v1.
  • If there is no user-facing approval surface, loopback redirects must require explicit trust configuration.
  • If there is no user-facing approval surface and open-internet CIMD is enabled, document the risk and provide an operator allowlist option.

Public CIMD clients

When token_endpoint_auth_method is none:

  • the client is treated as a public client;
  • the authorization-code flow must use PKCE;
  • PKCE method must be S256 in v1;
  • the token request must not include a client secret;
  • the token request must not include a client assertion.

Resource indicator policy

MCP clients are expected to use OAuth resource indicators. altinity-mcp should bind the requested resource into the authorization decision.

Requirements:

  • /authorize should require a resource parameter identifying this MCP protected resource, unless a temporary compatibility mode is explicitly enabled.
  • /token must reject a resource parameter that conflicts with the resource bound at /authorize.
  • The exact resource decision must be bound into the authorization code.
  • Resource comparison may tolerate configured trailing-slash normalization, but the bound value must be deterministic and documented.
  • Token audience binding remains a separate issue, but this change must not weaken existing resource checks.

Authorization-code binding

CIMD metadata can change between /authorize and /token. The authorization decision must therefore be bound to the authorization code.

At /authorize, after resolving and validating CIMD metadata, bind the following to the pending authorization state and to the issued authorization code:

  • exact client_id string;
  • validated metadata decision record, not only raw JSON;
  • metadata hash, if useful for logging/debugging;
  • metadata fetch time and cache decision;
  • selected token_endpoint_auth_method;
  • redirect_uri used in the authorization request;
  • PKCE code_challenge;
  • PKCE code_challenge_method;
  • requested resource;
  • requested scopes;
  • loopback redirect trust decision, if applicable;
  • consent or approval decision, if applicable.

At /token, validate that:

  • the code was issued to the same exact client_id;
  • the token request uses the same redirect_uri;
  • the PKCE verifier matches the bound challenge;
  • the token endpoint authentication method matches the bound metadata decision;
  • no client secret or client assertion is supplied for public CIMD clients;
  • the code has not expired;
  • the code replay policy below is satisfied;
  • the requested resource, if supplied at /token, matches the authorization decision.

The token endpoint must not make a less restrictive decision by re-fetching changed metadata. If metadata is re-fetched at /token, the new metadata must not broaden the authorization-code decision.

HA authorization-code replay model

altinity-mcp should not claim strict local single-use authorization-code enforcement while also requiring no shared storage across replicas. Without shared storage, a stateless JWE authorization code can be redeemed more than once within its TTL if the redeemer has the code and PKCE verifier.

For HA deployments without shared storage, use upstream authorization-code redemption as the replay boundary.

Required model for the brokered upstream OAuth flow:

  • Do not exchange the upstream authorization code in /oauth/callback.

  • In /oauth/callback, issue a downstream authorization code as a short-lived JWE.

  • The downstream authorization-code JWE must contain:

    • upstream authorization code;
    • upstream PKCE verifier;
    • exact CIMD client_id;
    • validated CIMD metadata decision record;
    • downstream redirect_uri;
    • downstream PKCE challenge and method;
    • requested resource;
    • requested scopes;
    • issue time and expiration time.
  • In /oauth/token, after validating the downstream client, redirect URI, PKCE verifier, resource, and metadata decision, exchange the upstream authorization code with the upstream IdP.

  • Treat upstream invalid_grant on repeated redemption as the replay protection result.

  • Do not return the same upstream bearer again from a replayed downstream authorization code.

  • Keep the downstream authorization-code TTL short, suggested maximum 60 seconds.

Tradeoff:

  • If /oauth/token successfully exchanges the upstream code but the response is lost before the client receives it, a retry may fail with invalid_grant. This is acceptable and more OAuth-compliant than replaying the same authorization code successfully.

If altinity-mcp later supports a fully self-issued authorization-server mode without an upstream stateful authorization server, strict single-use authorization-code enforcement requires one of:

  • shared storage or consensus for used-code tracking;
  • sticky sessions plus local replay cache, documented as weaker and not globally strict;
  • stateless short-TTL mitigation, documented as not strict OAuth single-use.

Only shared storage or an upstream stateful authorization server provides strict cross-replica single-use semantics.

Refresh-token policy

Refresh-token behavior for public CIMD clients must be explicit.

Requirements:

  • Do not advertise refresh_token in grant_types_supported for CIMD v1 unless refresh-token replay/reuse behavior is designed and tested.
  • If refresh tokens are issued to public CIMD clients, they must either be sender-constrained or rotated with replay/reuse detection by a stateful authority.
  • If altinity-mcp only wraps upstream refresh tokens, rely on upstream refresh-token rotation/reuse detection where available and document that dependency.
  • If upstream refresh-token rotation/reuse detection is not available, prefer not issuing refresh tokens to public CIMD clients in v1.
  • Refresh-token JWE claims must bind the exact client_id, selected resource/audience decision, scope, issuer, and expiration.

Caching

Valid CIMD metadata documents may be cached.

Requirements:

  • Cache key is the exact original client_id string.
  • Cache values must include the validated metadata and validation decision, not just raw JSON.
  • Respect HTTP cache headers where safe.
  • Honor Cache-Control: no-store by not storing the metadata beyond the current request.
  • Treat Cache-Control: no-cache as requiring refetch before reuse.
  • If no usable cache headers are present, use a short default TTL, suggested 5 minutes.
  • Cap positive cache TTL, suggested maximum 1 hour.
  • Do not use stale metadata for new authorization decisions after TTL expiry.
  • Do not cache error responses as valid metadata.
  • Do not cache invalid or malformed metadata as valid metadata.
  • Failed fetches may use short negative caching only for abuse control, suggested maximum 30 seconds.
  • Negative cache entries must not override an existing unexpired valid metadata cache entry.
  • Cache entries must be bounded by count and total memory size.
  • Cache behavior must not require cross-pod coordination.

SSRF risk

CIMD requires the authorization server to fetch a URL supplied by the OAuth client as client_id. Without strict controls, this can turn altinity-mcp into an SSRF probe.

Examples of unsafe targets an attacker could try to reach:

  • localhost services;
  • Kubernetes API endpoints;
  • cloud instance metadata services;
  • private RFC1918 addresses;
  • link-local addresses such as 169.254.169.254;
  • IPv6 loopback, link-local, and unique-local addresses;
  • internal DNS names;
  • services reachable only from the altinity-mcp network.

The metadata fetcher must therefore be treated as a security boundary, not as a generic HTTP client.

SSRF-safe fetcher and dialer

The implementation should use a dedicated CIMD/JWKS fetcher, not a generic default HTTP client.

Requirements:

  • Disable environment proxy use.

  • Resolve DNS explicitly.

  • Validate all resolved A and AAAA records before connecting.

  • Reject special-use IP ranges, including:

    • loopback;
    • private RFC1918 IPv4 ranges;
    • link-local;
    • multicast;
    • unspecified addresses;
    • IPv6 loopback;
    • IPv6 link-local;
    • IPv6 unique-local;
    • carrier-grade NAT and other non-public ranges where applicable.
  • Pin the connection to a validated IP address through the outbound dial.

  • Keep TLS SNI as the original hostname.

  • Keep the HTTP Host header as the original hostname.

  • Re-check the connected remote address before sending the HTTP request.

  • Do not allow internal DNS names to bypass IP validation.

  • Reject redirects.

  • Enforce body and decompressed-size limits.

  • Bound concurrent outbound metadata/JWKS fetches.

  • Rate-limit repeated failed fetches by hostname and/or source client where practical.

  • Avoid retry storms.

Optional metadata URLs

Optional metadata URLs are untrusted.

Requirements:

  • Do not fetch logo_uri, client_uri, tos_uri, or similar optional URLs in v1.
  • If optional URLs are displayed, display them as external untrusted URLs.
  • If optional URL fetching is added later, it must use the same SSRF-safe fetcher and must be separately threat-modeled.

Error handling and logging

Invalid CIMD metadata should fail closed.

Errors should be clear enough for operators and client developers to diagnose:

  • unsupported scheme;
  • query string not allowed;
  • dot segment not allowed;
  • unsupported port;
  • hostname not allowed;
  • blocked address;
  • fetch timeout;
  • redirect response;
  • oversized response;
  • non-JSON response;
  • missing required field;
  • invalid field type;
  • client_id mismatch;
  • redirect URI mismatch;
  • unsupported token endpoint authentication method;
  • unsupported client assertion or client secret for public CIMD;
  • invalid or replayed authorization code;
  • upstream invalid_grant during delegated authorization-code redemption.

Logging requirements:

  • Use structured logs.
  • Redact authorization codes, access tokens, refresh tokens, client assertions, client secrets, and full query strings.
  • Log the exact reason category.
  • Log the normalized hostname and policy decision.
  • Do not log full JWT assertions.
  • Do not log full upstream token responses.

Acceptance criteria

  • CIMD public clients can complete the MCP OAuth authorization-code flow with PKCE using token_endpoint_auth_method: "none".
  • Authorization server metadata advertises client_id_metadata_document_supported: true.
  • Authorization server metadata advertises token_endpoint_auth_methods_supported: ["none"].
  • Authorization server metadata advertises code_challenge_methods_supported including S256.
  • Authorization server metadata does not advertise registration_endpoint.
  • Authorization server metadata does not advertise shared-secret client authentication methods for CIMD.
  • DCR registration is removed or returns a clear unsupported/removed error during cleanup.
  • CIMD client_id URLs with query strings are rejected.
  • CIMD client_id URLs with dot segments or encoded dot segments are rejected.
  • Invalid CIMD metadata fails closed.
  • Metadata schema validation covers required fields, field types, field sizes, duplicate redirect URIs, and unsupported auth methods.
  • Client secrets, client assertions, and private_key_jwt are rejected for CIMD v1.
  • Authorization codes are bound to the validated CIMD metadata decision record.
  • HA authorization-code replay behavior is implemented by deferring upstream authorization-code exchange to /oauth/token, or another explicitly documented design with equivalent cross-replica single-use semantics.
  • Replaying a downstream authorization code after successful upstream redemption does not return the same bearer again.
  • Redirect URI validation implements the explicit non-loopback and loopback policies above.
  • Resource indicator validation binds the selected resource into the authorization decision.
  • Refresh-token behavior for public CIMD clients is either disabled in v1 or explicitly backed by upstream rotation/reuse detection or another stateful replay-control mechanism.
  • SSRF tests cover localhost, private IPs, link-local IPs, IPv6 local ranges, internal DNS names, redirects, oversized responses, non-JSON responses, encoded path edge cases, blocked ports, proxy bypass, and DNS rebinding-style cases where feasible.
  • Cache tests cover max-age, no-store, no-cache, missing cache headers, TTL cap, negative caching, and exact client_id cache keys.
  • Logs and metrics distinguish CIMD success, CIMD validation failure, CIMD fetch failure, SSRF rejection, unsupported client authentication, authorization-code replay/upstream invalid_grant, and DCR removed/unsupported route usage.
  • Documentation explains that CIMD replaced DCR.

Non-goals

  • Do not redesign the full OAuth authorization flow beyond the CIMD and replay requirements in this issue.
  • Do not add DPoP support in this issue.
  • Do not add shared-secret CIMD client authentication.
  • Do not add private_key_jwt support in this issue.
  • Do not fetch optional display assets such as logo_uri in v1.
  • Do not fully solve token audience binding here; that should remain a separate OAuth/resource-indicator issue.
  • Do not introduce mandatory shared storage for metadata caching.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions