Token Vault: Mint vs Broker
AuthPlane answers one question per resource: who signs the access token it vends?
- A Mint resource is a resource AuthPlane mints tokens for. AuthPlane is the issuer; the token is an
at+jwtJWT signed by AuthPlane’s JWKS; your MCP server validates it offline. - A Broker resource is backed by a third-party provider — GitHub, Google, Slack, Notion, Linear, Atlassian, or any generic OAuth 2.0 upstream. AuthPlane delegates token minting to that provider: it stores the user’s upstream credential encrypted at rest and vends the provider’s native token (e.g. a GitHub
gho_…) fresh on every request via RFC 8693 token exchange.
At a glance
Section titled “At a glance”| Mint | Broker (Token Vault) | |
|---|---|---|
| Who signs the token? | AuthPlane | The upstream provider |
| Token format | AuthPlane-signed JWT (at+jwt) | The provider’s native token (e.g. gho_… for GitHub; opaque elsewhere) |
| What the resource server validates against | AuthPlane’s JWKS | The provider’s API |
| Does AuthPlane hold provider credentials? | No | Yes — the upstream OAuth refresh token, encrypted at rest in broker_grants |
| Who consents? | The user consents once for the agent on this resource | The user consents on the agent and connects the provider |
| Typical use | Your MCP server | Calling third-party APIs as the user |
In the Mint case, the resource server never talks to a third party — it trusts AuthPlane’s signature. In the Broker case, AuthPlane is the OAuth broker: it holds the user’s encrypted refresh token, talks to the provider, and hands the agent a token the provider itself minted.
How the vault fills: the /connect/{provider} consent flow
Section titled “How the vault fills: the /connect/{provider} consent flow”Broker has two phases: a one-time per-user setup, then a per-call vend.
-
Direct the user’s browser to
GET /connect/{provider}with areturn_urllisted in yourconnect.allowed_return_urls:GET /connect/github?resource=github&return_url=https://app.example.com/connected -
AuthPlane redirects the browser to the provider’s authorize page with HMAC-signed state (the HMAC key comes from
AUTHPLANE_CONNECT_STATE_SECRET— required, it prevents CSRF in the Connect flow). -
The user approves the scopes at the provider.
-
The provider redirects back to
/connect/{provider}/callback. AuthPlane exchanges the code, then encrypts and stores the refresh-grant inbroker_grants.
How the vault vends: token exchange (RFC 8693)
Section titled “How the vault vends: token exchange (RFC 8693)”Every time an agent or MCP server needs a fresh upstream token, it calls POST /oauth/token with grant_type=urn:ietf:params:oauth:grant-type:token-exchange and resource=<broker-slug>. AuthPlane looks up the stored grant, refreshes the upstream access token with the provider, and returns it — in standard RFC 8693 response format, but the credential inside is the provider’s own token, not an AuthPlane JWT. (Requires token_exchange.enabled: true, or AUTHPLANE_TOKEN_EXCHANGE_ENABLED=true.)
Two consequences worth internalizing:
- Revocation is fast. When the user revokes the upstream OAuth grant at the provider, the next refresh fails — and the agent’s next call gets
consent_requiredimmediately. No long-lived stale tokens sitting in agent memory. - The agent never sees the upstream refresh token. Only the per-call access token. That keeps the blast radius of a leaked agent-side credential bounded.
At-rest encryption
Section titled “At-rest encryption”Plaintext refresh-grants never persist. The vault requires one of two data_encryption drivers (set via data_encryption.driver or AUTHPLANE_DATA_ENCRYPTION_DRIVER):
| Driver | How it works | Choose it when |
|---|---|---|
aes_master | AuthPlane encrypts with a 32-byte AES-256 master key (64 hex chars) read from an env var you name in data_encryption.aes_master.key_env. Supports rotation via an old_key_env decrypt-only fallback. | Simple single-host or HA-Postgres deploys. |
vault_transit_encrypt | Encryption is delegated to HashiCorp Vault’s Transit secrets engine — key material never leaves Vault. Token or AppRole auth. | FIPS/HSM requirements, strict segregation of duties, multi-replica deployments. |
See the upstream HashiCorp Vault Transit guide for the full recipe.
Per-resource exchange policy
Section titled “Per-resource exchange policy”Each Broker resource declares who may vend from it via resources.policy.exchange.allowed_client_ids (an empty list allows any consented client). Before vending, AuthPlane enforces three bounds:
requested scopes ⊆
consent_grants.scopes(per-agent attestation) ⊆broker_grants.scopes_granted(per-provider grant), and the acting client satisfiesallowed_client_ids.
resources: - slug: github backend_kind: broker broker_provider_slug: github scopes: - { name: repo, upstream: repo } - { name: read:user, upstream: read:user } policy: exchange: allowed_client_ids: ["mcp-server-prod"] # only the MCP server may vendIf any bound fails, POST /oauth/token returns consent_required with a cause (consent_missing or scope_insufficient) and a consent_url. For agent-side gaps the URL points at AuthPlane’s /authorize; for provider-side gaps it points at /connect/{provider} so the user can (re-)connect. Clients that don’t want to branch can simply open consent_url in all cases.
Mint fronting a Broker
Section titled “Mint fronting a Broker”A common combination: a Mint MCP server with its own local tools that also needs a vaulted provider token (say, a tools/summarize_my_prs tool needing GitHub). A fronting link — source_slug (the Mint), target_slug (the Broker), and a scope_map — authorizes the MCP server to exchange the agent’s Mint token for a Broker-vended one. The agent presents one token, the user consents once, and the agent never holds a GitHub token at all. Fronting links are admin-API-only (POST /admin/fronting on port 9001). Full walkthrough in the upstream Mint vs Broker doc.