MCP gateway → hidden Mint
At a glance. A gateway sits between agents and hidden downstream MCP infrastructure. Unlike client-credentials-hop, user identity IS preserved on the second hop via RFC 8693 token exchange, and the downstream sees an
act-claim chain naming the gateway. Uses fronting-links — an operator-vouching declaration that bypasses the runtime-client-binding gate for gateway-fronted patterns.
Topology
flowchart LR
User["User"]
AS["AuthPlane"]
Agent["Agent"]
Gateway["Gateway<br/>(MCP)"]
Hidden["Hidden Mint<br/>MCP<br/>(invisible to user)<br/>aud=hidden-mint<br/>sub=user<br/>act.sub=gateway"]
User -->|consent| AS
AS --> Agent
AS --> Gateway
Agent -->|"user token<br/>aud=gateway"| Gateway
Gateway -->|"RFC 8693 ex."| Hidden
Two Mint Resources — visible gateway and hidden downstream. A fronting-link declares that gateway is authorized to front tokens to downstream. Token on the second hop preserves sub=user but adds act.sub=gateway_id.
Issued-token shape (Option β)
The exchanged token that hits the hidden Mint:
{
"sub": "user-42", // preserved
"aud": "https://hidden-mint.internal/api",
"client_id": "<source resource slug>", // fronted-source, not gateway
"act": {
"sub": "<gateway_client_id>", // gateway is the acting party
"actor_type": "agent"
},
"scope": "internal/read"
}
sub = original user (audit trail). act.sub = the gateway that made the exchange call (who to hold accountable if the second hop misbehaves). client_id = the fronted-source slug (per Option β — see Concepts: Architecture § Unified Resource model).
Flow
sequenceDiagram
participant Agent
participant Gateway
participant AS as AuthPlane
participant Hidden as Hidden Mint
Agent->>AS: user auth-code → user token (aud=gateway)
Agent->>Gateway: POST /mcp Bearer <user token>
Gateway->>Gateway: validates the user token
Gateway->>AS: POST /oauth/token<br/>grant_type=urn:ietf:params:oauth:grant-type:token-exchange<br/>subject_token=<user token><br/>resource=hidden-mint ← target<br/>client_id=$GATEWAY_ID<br/>client_secret=$GATEWAY_SECRET
AS->>AS: checks fronting_link(gateway → hidden-mint) exists → bypass runtime-client-binding gate
AS-->>Gateway: exchanged token: sub=user, aud=hidden-mint, act.sub=gateway
Gateway->>Hidden: POST /api Bearer <exchanged token>
Hidden->>Hidden: validates the exchanged token
Hidden->>Hidden: executes with user identity + gateway act-chain visible
When to use
- Multiple agents access the same internal infra through one boundary (the gateway).
- You want user identity preserved on the second hop (unlike client-credentials-hop).
- You want the audit trail to name BOTH user AND gateway (act-chain).
- Downstream MCPs shouldn’t be directly reachable from agents.
Don’t use when:
- Downstream doesn’t need user identity → client-credentials-hop is simpler.
- Downstream wraps an upstream IdP → mcp-gateway-broker.
- No gateway needed (agent talks to MCPs directly) → direct-fanout.
How to configure
CLI:
# 1. Register both Resources
authserver admin resource create --slug gateway --uri https://gw.example.com/mcp --backend-kind mint --scopes 'tools/read||Read tools'
authserver admin resource create --slug hidden-mint --uri https://hidden.internal/api --backend-kind mint --scopes 'internal/read||Internal read'
# 2. Create the fronting link — gateway may front tokens to hidden-mint
authserver admin fronting create \
--source gateway \
--target hidden-mint \
--scope-map "tools/read:internal/read"
# 3. Register the gateway as a confidential client with token-exchange
authserver admin client create \
--name gateway \
--grant-types authorization_code,refresh_token,urn:ietf:params:oauth:grant-type:token-exchange \
--auth-method client_secret_post \
--scopes 'tools/read||Read tools' \
--scopes 'internal/read||Internal read'
--scope-map on the fronting-link tells AuthPlane how to map source-side scopes to target-side scopes during the exchange. Format: SOURCE:TARGET,SOURCE:TARGET. CLI caveat: the --scope-map flag splits on the FIRST :, so source-side scope names that themselves contain : (like tool:list) can’t be expressed via CLI — use REST/YAML for those.
Enable token-exchange at boot: AUTHPLANE_TOKEN_EXCHANGE_ENABLED=true.
How AuthPlane handles it
- Standard user auth-code flow issues a token with
aud=gateway. - On the exchange call,
TokenExchangeServicelooks upfronting_links(source=gateway, target=hidden-mint). Found → bypass the runtime-client-binding gate (that gate lives on the broker dispatch path — see Guides: Runtime client binding). MintIssuersigns the exchanged token per Option β shape:subpreserved from subject,client_id= fronted-source slug,act.sub= the exchange caller’s client_id.issuancesaudit row records both parties + the exchange chain.
Multi-hop is supported — chained exchanges (A → B → C) nest act claims. See Concepts: Delegation & act-chain.
Verify
# Confirm the fronting link
authserver admin fronting list
# → gateway → hidden-mint, scope-map: tools/read:internal/read
# Trigger an exchange and inspect the resulting act chain
authserver admin issuance list --client gateway --limit 3
# → one row with sub=user aud=hidden-mint act.sub=gateway
echo "<exchanged_token>" | cut -d. -f2 | base64 -d | jq '{sub, aud, client_id, act}'
See also
- Concepts: Delegation & act-chain
- Guides: Runtime client binding — the gate this topology bypasses via fronting-link
- Topologies: Client-credentials hop — the simpler alternative that drops user identity
- Topologies: MCP gateway → broker — broker variant of gateway fronting