RFC compliance

TL;DR — AuthPlane implements the subset of each RFC needed for MCP authorization: not less, not more. Every deviation is opt-in, documented, and tied to an ADR. Legacy grants (implicit, ROPC) are intentionally omitted per OAuth 2.1 security guidance. This page is the compliance statement — one row per RFC with what’s covered, what’s not, and why.

Design philosophy

Every RFC below is implemented to the letter unless the “Deviation” column says otherwise. Where an RFC offers multiple approaches (token formats, client auth methods), AuthPlane picks the secure-by-default option. The canonical per-RFC compliance statement, with the conformance evidence behind it, is authserver/docs/reference/compliance.md; this page tracks it.

Core OAuth

RFC 6749 — OAuth 2.0 Authorization Framework

Implemented sections: §4.1 (Authorization Code Grant), §4.4 (Client Credentials), §3.3 (Scope), §5.2 (Error responses).

Coverage:

  • Authorization Code grant with mandatory PKCE (RFC 7636)
  • Client Credentials grant (RFC 6749 §4.4) — see below
  • Scope validation against registered scopes
  • Client authentication: none, client_secret_basic, client_secret_post
  • Error responses per §5.2

Configurable default-scope handling (ADR-012): when oauth.require_scope: false, missing scope in authorize requests defaults to the resource’s registered scopes. RFC 6749 §3.3 explicitly allows this: “If the client omits the scope parameter … the authorization server MUST either process the request using a pre-defined default value or fail the request indicating an invalid scope.” We ship the fail branch by default (spec-strict); the toggle switches on the pre-defined default branch that the RFC also permits.

Not implemented: Implicit grant (§4.2), Resource Owner Password Credentials (§4.3) — OAuth 2.1 removes both from the spec.

RFC 6749 §4.4 — Client Credentials

Machine-to-machine token issuance. Requires client_secret_post or client_secret_basic. Supports scope validation and resource audience binding.

Configuration: client_credentials.enabled — on by default since v0.2.0, disabled by default through v0.1.x. Only confidential clients with client_credentials in their grant_types.

Token format: RFC 9068 JWT with typ: at+jwt, sub = client_id (no user), aud bound to resource when set. Introspection + revocation supported.

RFC 7636 — PKCE

S256 only. plain method rejected. Missing code_challenge rejected.

RFC 9700 — OAuth 2.0 Security BCP

Coverage: PKCE required, exact redirect-URI matching, refresh-token rotation with reuse detection, no implicit grant, DPoP available.

Token format

RFC 9068 — JWT Profile for OAuth 2.0 Access Tokens

Access tokens are JWTs with typ: at+jwt, standard claims (iss, sub, aud, exp, iat, jti, client_id, scope).

RFC 7517 — JSON Web Key (JWK)

JWKS endpoint at /.well-known/jwks.json. Supports ES256 (EC P-256) and RS256 key types.

Discovery

RFC 8414 — OAuth 2.0 Authorization Server Metadata

Full AS metadata at /.well-known/oauth-authorization-server. Fields returned:

  • issuer, authorization_endpoint, token_endpoint, registration_endpoint, revocation_endpoint
  • introspection_endpoint (when introspection enabled)
  • jwks_uri
  • response_types_supported, grant_types_supported, token_endpoint_auth_methods_supported
  • code_challenge_methods_supported: ["S256"]
  • scopes_supported (aggregated from all resources’ scopes)
  • resource_indicators_supported: true
  • client_id_metadata_document_supported: true (CIMD)
  • authorization_response_iss_parameter_supported: true — always emitted, never omitted (RFC 9207, since v0.2.0)
  • authorization_grant_profiles_supported — lists urn:ietf:params:oauth:grant-profile:id-jag (since v0.2.0)
  • dpop_signing_alg_values_supported (when DPoP enabled)
  • authplane_agent_identity_supported: true (AuthPlane extension)

The same document is aliased at /.well-known/openid-configuration.

RFC 9207 — Authorization Server Issuer Identification

Since v0.2.0, every authorization response — success and error alike — carries iss set to the AS issuer identifier (§2), and AS metadata emits authorization_response_iss_parameter_supported: true explicitly rather than omitting it, so a client applying §2.4’s strict policy can reject a response that arrives without iss. The value is written verbatim; clients compare it byte for byte. The MCP 2026-07-28 specification lists this as SHOULD, announced to become MUST.

RFC 9728 — OAuth 2.0 Protected Resource Metadata

Served by the resource server (i.e., your MCP server via the SDK) at /.well-known/oauth-protected-resource/<resource-path> (§3.1 path-insertion form). MCP clients use this to discover the authorization server. AuthPlane’s SDKs generate and serve this automatically.

Since v0.2.0 the AS also serves the document for every registered Resource — GET /.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/{ref} — so a resource server that cannot host well-known paths itself still has a conformant document to point its WWW-Authenticate: resource_metadata at.

Client registration

RFC 7591 — Dynamic Client Registration

Three modes:

  • open — anyone can register (dev only)
  • approved_redirects — anyone can register, redirect URI must match dcr.approved_redirects patterns
  • admin_only — pre-registration required via Admin API/CLI

Fields supported: redirect_uris, client_name, token_endpoint_auth_method, grant_types, scope, and AuthPlane extensions for agent identity.

draft-ietf-oauth-client-id-metadata-document — CIMD

When client_id is a URL, AuthPlane fetches the metadata document at that URL, validates fields, and uses it for registration. Configurable via cimd.require_https (default true), cimd.cache_ttl, cimd.fetch_timeout.

Resource indicators

RFC 8707 — Resource Indicators for OAuth 2.0

The resource parameter in /oauth/authorize and /oauth/token binds tokens to a specific resource server via the aud claim.

Strict exact-string matching — trailing slashes matter. See Configuration: Resources for the URI-mismatch trap.

Token lifecycle

RFC 7009 — Token Revocation

Revocation endpoint at /oauth/revoke. Accepts both access tokens and refresh tokens. Returns 200 on success and for invalid/unknown tokens per §2.2; error responses (unsupported_token_type, client-auth failure, 503) follow §2.2.1.

RFC 7662 — Token Introspection

Introspection endpoint at /oauth/introspect. Accepts access tokens and machine tokens. Requires client authentication (client_secret_post or client_secret_basic) for confidential clients.

Refresh token rotation

Refresh tokens rotate on every use — a new token is issued, the old one consumed. Reuse detection — a second use of a consumed refresh token revokes the entire token family (RFC 9700 §4.14).

Error format

RFC 9457 — Problem Details for HTTP APIs

Error responses include both OAuth error fields (error, error_description) and Problem Details fields (type, title, detail, status). Content-Type: application/problem+json.

Full error catalog in Reference: Errors.

Proof of possession

RFC 9449 — OAuth 2.0 Demonstrating Proof of Possession (DPoP)

Full support including proof validation, token binding, and server nonces.

Coverage:

  • DPoP proof JWT validation (§4.3): typ, alg, jwk, htm, htu, iat, jti, nonce
  • Supported algorithms: ES256, RS256, PS256
  • Algorithm restriction: alg:none and all symmetric algorithms rejected
  • Private key in jwk header rejected
  • htu comparison strips query string (scheme + authority + path; authority includes non-default port; compared after RFC 3986 normalization)
  • JKT computation per RFC 7638 (JWK Thumbprint) — base64url-encoded SHA-256
  • Token binding via cnf.jkt claim in access tokens
  • DPoP-bound token type: token_type: DPoP
  • Server-issued nonces (DPoP-Nonce response header) with configurable TTL
  • JTI replay prevention with database-backed store + background purge
  • ath (access token hash) validation on resource requests
  • Backward compatible: no DPoP proof → standard Bearer token
  • Introspection returns cnf.jkt for DPoP-bound tokens
  • AS metadata advertises dpop_signing_alg_values_supported

Configuration: dpop.enabled — on by default since v0.2.0, disabled by default through v0.1.x.

Token exchange

RFC 8693 — OAuth 2.0 Token Exchange

Full support for impersonation and delegation flows.

Coverage:

  • grant_type=urn:ietf:params:oauth:grant-type:token-exchange
  • Subject token validation: signature, issuer, expiry, revocation
  • Subject token types: access_token, jwt URNs
  • Impersonation: no actor token, no act claim, sub preserved
  • Delegation: actor token present, nested act claim per §4.1
  • Multi-hop delegation: correct chain nesting
  • Scope narrowing: requested scope must be subset of subject-token scope
  • Configurable chain depth limit (1-10, default 5)
  • Per-resource policy enforcement (policy.exchange.allowed_client_ids)
  • Self-exchange guarded by token_exchange.allow_self_exchange
  • DPoP binding propagation on exchanged tokens
  • AS metadata: grant_types_supported includes the URN

Configuration: token_exchange.enabled — on by default since v0.2.0, disabled by default through v0.1.x.

AuthPlane extensions

JWT Bearer + XAA (RFC 7523 + ID-JAG assertion profile)

Enterprise-Managed Authorization via JWT Bearer grant with the ID-JAG assertion format — the stable Enterprise-Managed Authorization extension of the MCP Authorization spec (2026-07-28).

Coverage:

  • grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
  • ID-JAG assertion validation: signature, issuer, audience, expiry, type header (oauth-id-jag+jwt)
  • Trusted IdP registry with JWKS discovery + caching (SSRF-protected)
  • Policy engine: IdP + client_id + scope + resource constraint evaluation
  • Subject mapping: auto_map (federated subject) and strict (explicit mapping required) modes
  • Replay prevention: assertion JTI single-use enforcement + auto-purge
  • Scope intersection: policy scopes narrow the issued token’s scope
  • Resource binding: resource parameter flows to token aud claim
  • DPoP binding: XAA tokens support cnf.jkt
  • Machine token storage for revocation/introspection
  • AS metadata: grant_types_supported includes the jwt-bearer URN, and authorization_grant_profiles_supported lists urn:ietf:params:oauth:grant-profile:id-jag (since v0.2.0) — the field the extension tells clients to check

Configuration: xaa.enabled — on by default since v0.2.0 (the grant validates nothing until a trusted IdP is registered), disabled by default through v0.1.x, where the block is YAML-only. AUTHPLANE_XAA_* env vars exist since v0.2.0.

No deviations from RFC 7523 §2.1 (JWT assertion profile).

Agent identity claims

AuthPlane-specific JWT extensions:

  • agent_id — set to client_id when issuing client has is_agent=true
  • agent_chain — ordered list built from delegation act chain, capped at 8
  • Agent registration via DCR (agent: true, agent_description max 255 chars)
  • Optional JWKS agent listing (agents.enable_jwks_listing: true)
  • AS metadata advertises authplane_agent_identity_supported: true

Not standardized. See Concepts: Agent identity.

MCP-specific

MCP Authorization Specification (2026-07-28)

Implemented: the full authorization-server side of the 2026-07-28 revision, and the stable Enterprise-Managed Authorization extension.

RequirementLevelWhere
Protected Resource Metadata (RFC 9728) — served by the resource server and, since v0.2.0, by the AS for any registered ResourceMUSTRFC 9728 above
AS metadata discovery (RFC 8414), aliased at /.well-known/openid-configurationMUSTRFC 8414 above
Client ID Metadata Documents — on by default; https + path component required since v0.2.0MUSTCIMD above
Dynamic Client Registration with application_typeDeprecated by the spec, retainedRFC 7591 above
Authorization code + PKCE (S256), resource indicator (RFC 8707) bound to aud, enforced at both endpointsMUSTRFC 7636 / RFC 8707 above
iss on authorization responses (RFC 9207)SHOULD → MUSTRFC 9207 above
DPoP (RFC 9449), advertised and accepted by default since v0.2.0—RFC 9449 above
Enterprise-Managed Authorization: ID-JAG via jwt-bearer, advertised in authorization_grant_profiles_supportedExtension (stable)XAA above

Out of scope on the AS side: the non-auth bulk of 2026-07-28 (stateless core, server/discover, subscriptions, cache hints) is MCP server and SDK territory; minting ID-JAGs is the enterprise IdP’s role in that three-party model; the OAuth Client Credentials ext-auth extension is still draft and not committed to.

Tested against (v0.2.0, by hand, full flow through tools/call): Claude Desktop (CIMD, hosted callback), Claude Code (CIMD, loopback callback), MCP Inspector (DCR). Per-client detail in mcp-client-compatibility.md.

OAuth 2.1 itself remains an active IETF draft (draft-ietf-oauth-v2-1) rather than a published RFC — AuthPlane tracks the latest revision.

Summary matrix

RFCCoverageDeviationConfiguration
RFC 6749§4.1, §4.4, §3.3, §5.2ADR-012 (opt-in)Always on
RFC 7636 (PKCE)S256 onlyplain rejected (per BCP)Always on
RFC 7009 (Revocation)FullNoneAlways on
RFC 7517 (JWK)ES256, RS256NoneAlways on
RFC 7523 (JWT Bearer)Full assertion profileNonexaa.enabled
RFC 7591 (DCR)FullNonedcr.mode
RFC 7662 (Introspection)FullNoneAlways on
RFC 8414 (AS Metadata)FullNoneAlways on
RFC 8693 (Token Exchange)FullNonetoken_exchange.enabled
RFC 8707 (Resource Indicators)Full (exact match)NoneAlways on
RFC 9068 (JWT AT)FullNoneAlways on
RFC 9207 (iss on responses)Full (since v0.2.0)NoneAlways on
RFC 9449 (DPoP)FullNonedpop.enabled
RFC 9457 (Problem Details)FullNoneAlways on
RFC 9700 (OAuth BCP)FullNoneAlways on
RFC 9728 (PRM)Full (SDK; AS-served too since v0.2.0)NoneAlways on
CIMD draftFullNonecimd.enabled