Upstream connections
TL;DR — Broker providers are the abstraction AuthPlane uses to vend third-party access. Three protocol adapters:
oauth(GitHub, Slack, Google, any RFC 6749 provider),api_key(static per-user API key with rotation semantics),service_account(impersonation via a service account credential). Each provider’sconfig_datashape is protocol-specific. This guide covers the per-protocol setup + the Connect flow policy knobs.
When to use each protocol
Registering a oauth provider
Standard OAuth 2.0 flow. config_data fields:
broker_providers:
- slug: github
display_name: GitHub
protocol: oauth
config_data:
client_id: "your-github-oauth-app-client-id"
client_secret_ref: CONNECTOR_GITHUB_SECRET # env var name; NOT the secret itself
authorize_url: https://github.com/login/oauth/authorize
token_url: https://github.com/login/oauth/access_token
response_format: standard # one of: standard, form
extra_auth_params: # optional; per-provider
access_type: offline # Google refresh token
prompt: consent # forces refresh_token when combined with access_type=offline
Register the upstream OAuth app first — set callback URL to https://<your-authserver>/connect/{slug}/callback (e.g., /connect/github/callback). Then copy the client_id and secret name into config_data.
Registering an api_key provider
For services with no OAuth flow — user pastes an API key into the Connect UI:
broker_providers:
- slug: anthropic
display_name: Anthropic API
protocol: api_key
config_data:
header_name: X-Api-Key # required — header the key is sent under
header_prefix: "" # optional — e.g., "Bearer " for Authorization
issuance_instructions_url: https://console.anthropic.com/settings/keys # optional — link shown on Connect form
Vending returns the stored key as access_token plus the header_name / header_prefix so the SDK can present it correctly. No refresh — the key is static until the user rotates it by re-running the Connect flow (GET /connect/{slug}) with a new key.
Registering a service_account provider
For machine identity — one credential per provider, per-user consent still recorded:
broker_providers:
- slug: gcp-sa
display_name: GCP Service Account
protocol: service_account
config_data:
token_url: https://oauth2.googleapis.com/token
sa_email: mcp-broker@your-project.iam.gserviceaccount.com
sa_key_ref: GCP_SERVICE_ACCOUNT_JSON # env var name holding the JSON credential
The service account credential is stored once; per-user consent grants are still tracked in consent_grants for audit purposes. Vending returns a fresh short-lived Google token minted from the service account.
Attaching a broker provider to a Broker resource
Every broker provider needs at least one Broker resources: entry:
resources:
- slug: github-repos
display_name: GitHub Repo Access
backend_kind: broker
broker_provider_slug: github # reference by slug
scopes:
- name: repo:read
description: Read the user's repositories
upstream: repo # scope requested from GitHub
- name: repo:write
description: Push commits and create PRs
upstream: "repo,user" # multi-scope upstream mapping
The scopes[].name is what AuthPlane consumers see (in consent screens, PRM scopes_supported); upstream is what AuthPlane requests from the provider. Multi-scope upstream lets one AuthPlane scope cover multiple provider scopes atomically.
Connect flow — policy knobs
Global connect: config:
connect:
state_secret: AUTHPLANE_CONNECT_STATE_SECRET
allowed_return_urls: # global default
- http://localhost:*
- https://myapp.example.com/*
redirect_base_url: http://localhost:9000 # base for OAuth callbacks
Per-resource override — each Broker resource can narrow the return URLs:
resources:
- slug: github-repos
backend_kind: broker
broker_provider_slug: github
policy:
connect:
allowed_return_urls:
- https://myapp.example.com/gh-callback # only THIS URL for this resource
User’s-eye view of Connect
Direct the user’s browser to:
GET /connect/github?return_url=http://localhost:3000/connected
Flow:
- AS renders/redirects to a login page if the user isn’t signed in.
- AS redirects to GitHub’s
authorize_urlwith client_id + scopes + state token. - User approves at GitHub.
- GitHub redirects to
/connect/github/callback?code=...&state=.... - AS validates state, exchanges code at GitHub’s
token_url, encrypts the refresh-grant, writes abroker_grantsrow. - AS redirects browser to
return_url(validated againstallowed_return_urls).
Listing + disconnecting
User endpoints (session cookie auth):
GET /connections # user's session cookie — list connected providers
DELETE /connections/{provider} # user disconnects a single provider
GET /connect/{provider} # re-running Connect rotates the key / re-consents
Admin endpoints (API key auth):
GET /admin/users/{id}/grants # all consent + broker grants for a user
DELETE /admin/grants/broker/{id} # revoke a specific broker grant
DELETE /admin/grants/consent/{id} # revoke a per-agent consent attestation
Revoking a broker grant clears AuthPlane’s local record and stops future vends — it does not cascade a revocation to the upstream provider (upstream tokens are not AS-revocable). The user must also disconnect at the provider if that matters.
Encryption at rest
Every broker_grants row is encrypted before write. Driver picked via data_encryption: — either AES-256-GCM with HKDF-derived per-purpose subkeys (aes_master), or delegated to Vault Transit (vault_transit_encrypt). See Guides: Wire up the Token Vault → Step 1 and Operate: Vault Transit.
Concurrent vends and refresh
Upstream tokens expire; AuthPlane refreshes using the stored refresh-grant. Under concurrent vend load:
- Two vends arrive simultaneously, both need refresh — optimistic locking serializes. Second returns HTTP
423 Locked. Retry once on 423 — SDK clients do this automatically. - Refresh fails (upstream rejected the refresh grant — user revoked access, credential expired, etc.) — HTTP
400 consent_required cause=consent_missing,consent_urlback to/connect/{provider}. SDK translates to MCP-32042.
Security considerations
- Never put secrets directly in
config_data— always use the_refvariants (client_secret_ref,sa_key_ref), which point at env-var names rather than the secret itself. The YAML config is deliberately not marked secret; the env vars are. allowed_return_urlsis the open-redirect defense — never leave it empty in production. Broad patterns likehttps://*.example.com/*are fine;*alone is not.state_secretmust be 32+ bytes — HMAC-signed state tokens prevent CSRF on the callback.- Broker grants are encrypted at rest — data-encryption is a hard requirement, not optional, when
broker_providers:is non-empty. Boot fails otherwise.
Related
- Guides: Wire up the Token Vault — the higher-level “get me a GitHub token from my tool” walkthrough
- Concepts: Token Vault — three-bound consent model in depth
- Concepts: Grants & flows — the token-exchange grant used for vending
- Reference: Configuration → broker_providers — every field with defaults
- Topologies: Agent + brokered MCP — end-to-end topology
- Reference: Errors → Broker/Vault — full error catalog for this path