Skip to content

DEP for Enterprise-Managed Authorization for MCP (Resource Authorization Server / RFC 7523 jwt-bearer)#4842

Open
loafoe wants to merge 2 commits into
dexidp:masterfrom
philips-forks:dep-ema-mcp-resource-as
Open

DEP for Enterprise-Managed Authorization for MCP (Resource Authorization Server / RFC 7523 jwt-bearer)#4842
loafoe wants to merge 2 commits into
dexidp:masterfrom
philips-forks:dep-ema-mcp-resource-as

Conversation

@loafoe

@loafoe loafoe commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Overview

This branch covers the Resource Authorization Server role of the MCP Enterprise-Managed Authorization (EMA) extension, and has grown to include a foundational piece that role depends on: RFC 8707 Resource Indicators.

It now contains two related parts:

  1. DEP (docs) — Dex accepting an Identity Assertion JWT Authorization Grant (ID-JAG) via the JWT Bearer grant (RFC 7523) and issuing a resource-bound access token.
  2. RFC 8707 implementation (code) — making Dex honor the OAuth 2.0 resource parameter and bind access-token audiences to it. This is the linchpin the EMA flow relies on, and was previously a Future Improvement in the DEP.

Part 1 — DEP: EMA Resource Authorization Server (RFC 7523 jwt-bearer)

Complement to DEP #4600 (ID-JAG issuance, PR #4611), whose description explicitly scopes out "Phase 2 (accepting ID-JAG as upstream identity via RFC 7523)". This DEP specifies exactly that phase, so Dex can sit in front of MCP servers as the authorization server in an enterprise-managed deployment.

Key points:

  • New grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer at the existing /token endpoint
  • Validates the ID-JAG against configured trusted issuers (JWKS signature, oauth-id-jag+jwt typ, issuer, audience per EMA §4, expiry, optional client_id allow-list)
  • Issues an access token audience-restricted to the MCP server named by the ID-JAG resource claim (EMA §5.1)
  • Discovery extended with authorization_grant_profiles_supported advertising the urn:ietf:params:oauth:grant-profile:id-jag profile (EMA §6)
  • Disabled by default; opt-in via a new oauth2.enterpriseManagedAuthorization config block. No behavior change for existing deployments.

Part 2 — RFC 8707 Resource Indicators (implementation)

Dex previously dropped the OAuth 2.0 resource parameter silently and always set a token's aud to the client_id. RFC 8707 clients — including MCP clients using Dynamic Client Registration — could therefore never obtain a resource-restricted access token. Since EMA's whole point is binding the audience to the MCP server, this had to be addressed directly.

The subtlety: in Dex the access token is a signed JWT produced by the same builder as the ID token. OIDC Core requires the ID token's aud to be the client, while RFC 8707 wants the access token's aud to be the resource. So the two now diverge when a resource is requested:

  • access token: aud = requested resource(s), azp = client_id
  • ID token: aud = client_id (unchanged, OIDC-valid)

What's included:

  • parseResourceParams with RFC 8707 §2 validation (absolute URI, no fragment); invalid values return the invalid_target error code.
  • Resource []string threaded through AuthRequest, AuthCode, RefreshToken, and DeviceRequest across all storage backends (ent schemas + regenerated code, Kubernetes types, in-memory). Ent auto-migration adds the columns.
  • resource honored in the authorization-code, refresh, and device-code flows. Other flows pass nil and are unchanged.
  • getAudience and cross-client scopes untouched. With no resource, issued tokens are byte-for-byte identical to before — covered by a regression test.
  • Tests: parseResourceParams (valid / multiple / relative / non-URI / fragment), access-token-vs-ID-token audience divergence, multiple resources, the no-resource regression guard, and two cases in TestParseAuthorizationRequest. Full suite green across sqlite, postgres, etcd, kubernetes, and memory backends.

No IANA-registered discovery flag exists for RFC 8707, so no discovery metadata change is made; Dex simply honors resource when sent. (The MCP-relevant advertisement is RFC 9728 protected-resource metadata, tracked separately.)


Special notes for your reviewer

This PR has grown beyond the original docs-only DEP — Part 2 is a real, tested code change. The DEP (Part 1) builds on and assumes DEP #4600 / PR #4611; its reference implementation will follow as separate code. Part 2 (RFC 8707) stands on its own and is independently reviewable/mergeable.

Does this PR introduce a user-facing change?

Dex now supports RFC 8707 Resource Indicators: when a client supplies the `resource` parameter, the issued access token's audience is bound to the requested resource(s) instead of the client. ID token audiences are unchanged. Requests without `resource` behave exactly as before.

…ion Server role)

Proposes the Resource Authorization Server side of the MCP EMA flow:
accepting an ID-JAG via the JWT Bearer grant (RFC 7523) and issuing a
resource-bound access token. Complements DEP 4600 (ID-JAG issuance).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Andy Lo-A-Foe <andy.loafoe@gmail.com>
@loafoe loafoe force-pushed the dep-ema-mcp-resource-as branch from 5dbe07e to c1fa430 Compare June 19, 2026 18:03
Dex silently dropped the OAuth 2.0 "resource" parameter and always bound an
access token's audience to the client_id, so MCP clients (and any RFC 8707
client) could not obtain a resource-restricted access token. This surfaced
while building out the EMA Resource Authorization Server role: the audience
binding is the linchpin of the EMA flow.

Bind the access token's audience to the requested resource(s) when present,
while keeping the ID token's aud equal to the client_id (OIDC Core). The two
tokens diverge: access-token aud = resource, azp = client_id.

- Add parseResourceParams with RFC 8707 §2 validation (absolute URI, no
  fragment); invalid values return the invalid_target error code.
- Thread Resource []string through AuthRequest, AuthCode, RefreshToken and
  DeviceRequest across all storage backends (ent schemas + regenerated code,
  Kubernetes types, in-memory). Ent auto-migration adds the columns.
- Honor resource in the authorization-code, refresh, and device-code flows.
  Other flows pass nil and are unchanged.
- getAudience and cross-client scopes are untouched; with no resource the
  issued tokens are byte-for-byte identical to before (regression test).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant