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’s config_data shape is protocol-specific. This guide covers the per-protocol setup + the Connect flow policy knobs.

When to use each protocol

ProtocolUse caseExamples
oauthThird-party services that support OAuth 2.0 / 2.1 with refresh tokensGitHub, Slack, Google, Linear, Notion, Atlassian
api_keyServices that only support static API keys — user pastes their key into the Connect UI, AuthPlane stores it encryptedAnthropic, OpenAI, weather APIs, internal legacy APIs
service_accountMachine identity — one credential shared across users (with per-user consent recorded separately)GCP service accounts, machine tokens for internal APIs

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:

  1. AS renders/redirects to a login page if the user isn’t signed in.
  2. AS redirects to GitHub’s authorize_url with client_id + scopes + state token.
  3. User approves at GitHub.
  4. GitHub redirects to /connect/github/callback?code=...&state=....
  5. AS validates state, exchanges code at GitHub’s token_url, encrypts the refresh-grant, writes a broker_grants row.
  6. AS redirects browser to return_url (validated against allowed_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_url back to /connect/{provider}. SDK translates to MCP -32042.

Security considerations

  • Never put secrets directly in config_data — always use the _ref variants (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_urls is the open-redirect defense — never leave it empty in production. Broad patterns like https://*.example.com/* are fine; * alone is not.
  • state_secret must 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.