Configuration

TL;DR — AuthPlane loads config in three layers, highest priority last: built-in defaults → YAML file → AUTHPLANE_* env vars. Everything you need to boot is optional (SQLite + auto-generated keys + no config file); everything you need for production is explicit and validated at boot. This page lists every section, every key, and every env var.

Precedence

  1. Defaults — sensible values for local development, built into the binary.
  2. YAML file — authserver serve --config config.yaml.
  3. Environment variables — AUTHPLANE_* prefix; override YAML.

Env vars win because you want the same YAML file across environments with per-deployment overrides for secrets and issuer URLs.

Comma-separated list variables (like AUTHPLANE_OIDC_SCOPES=openid,email,profile) split on commas. Duration variables accept Go duration strings (10s, 5m, 1h, 24h). Bool variables accept true, false, 1, 0.

Minimal dev config

Zero config works — the binary starts on :9000 with SQLite, auto-generated signing keys, and no resource. The moment you need to accept a real MCP client, add a resource:

resources:
  - slug: my-mcp-server
    uri: http://localhost:3000/
    backend_kind: mint
    display_name: My MCP Server
    scopes:
      - name: tools/echo
        description: Echo tool

Or a single Mint resource via env vars:

export AUTHPLANE_RESOURCE_URI=http://localhost:3000/
export AUTHPLANE_RESOURCE_SCOPES=tools/echo
authserver serve

Minimal production config

Boot validation enforces these when server.issuer is not localhost:

server:
  issuer: https://auth.example.com

session:
  secret: "generate-a-32-byte-random-string"   # openssl rand -hex 32
  secure: true

admin:
  api_key: "generate-a-secure-api-key"          # openssl rand -hex 32

Anything missing here fails boot with a structured error — you cannot accidentally deploy a non-local AuthPlane with a random ephemeral session secret.


server

Public HTTP server settings.

server:
  issuer: http://localhost:9000     # Public URL — appears in AS metadata and JWT `iss`
  address: ":9000"                  # Listen address
  read_timeout: 30s
  write_timeout: 30s
  idle_timeout: 120s
  shutdown_wait: 10s                # Graceful shutdown drain
  allowed_origins: []               # CORS allowlist for browser MCP clients
Env varField
AUTHPLANE_SERVER_ISSUERserver.issuer
AUTHPLANE_SERVER_ADDRESSserver.address
AUTHPLANE_SERVER_READ_TIMEOUTserver.read_timeout
AUTHPLANE_SERVER_WRITE_TIMEOUTserver.write_timeout
AUTHPLANE_SERVER_IDLE_TIMEOUTserver.idle_timeout
AUTHPLANE_SERVER_SHUTDOWN_WAITserver.shutdown_wait
AUTHPLANE_SERVER_ALLOWED_ORIGINSserver.allowed_origins (comma-separated; * for all)

issuer appears in the /.well-known/oauth-authorization-server metadata and as the iss claim in every JWT. MCP clients use it for discovery. Must match the URL clients use to reach AuthPlane exactly — no trailing slash.

allowed_origins — empty by default. Browser-based MCP clients (MCP Inspector, Claude Desktop web) fail CORS preflight silently without it. For local dev set AUTHPLANE_SERVER_ALLOWED_ORIGINS=*; for production set an explicit allowlist. Server-to-server-only deployments may leave this empty.


storage

Persistence layer. Both backends implement the same interfaces — choose by driver.

storage:
  driver: sqlite                    # "sqlite" or "postgres"
  sqlite:
    path: data/authserver.db
    wal: true                       # WAL mode for concurrent reads
  postgres:
    dsn: "postgres://user:pass@localhost:5432/authserver?sslmode=require"
    max_conns: 25
    min_conns: 5
    max_conn_lifetime: 1h
    max_conn_idle_time: 30m
Env varField
AUTHPLANE_STORAGE_DRIVERstorage.driver
AUTHPLANE_STORAGE_SQLITE_PATHstorage.sqlite.path
AUTHPLANE_STORAGE_SQLITE_WALstorage.sqlite.wal
AUTHPLANE_STORAGE_POSTGRES_DSNstorage.postgres.dsn
AUTHPLANE_STORAGE_POSTGRES_MAX_CONNSstorage.postgres.max_conns
AUTHPLANE_STORAGE_POSTGRES_MIN_CONNSstorage.postgres.min_conns
AUTHPLANE_STORAGE_POSTGRES_MAX_CONN_LIFETIMEstorage.postgres.max_conn_lifetime
AUTHPLANE_STORAGE_POSTGRES_MAX_CONN_IDLE_TIMEstorage.postgres.max_conn_idle_time

SQLite is default and recommended for single-instance deployments. Pure Go via modernc.org/sqlite (no CGO). WAL mode enables concurrent reads while writes serialize. DB file is created automatically on first boot.

PostgreSQL is required for multi-instance (HA) deployments. Uses LISTEN/NOTIFY for cross-instance signing-key rotation. Run authserver migrate before first start.


signing

JWT signing keys and algorithms.

signing:
  algorithm: ES256                  # "ES256" (default) or "RS256"
  key_store: keyfile                # "keyfile", "postgres_key", or "vault_transit"
  key_path: data/keys               # Keyfile store: directory for PEM files
  vault_transit:                    # Only when key_store: vault_transit
    address: https://vault:8200
    token: ""                       # Static token (mutually exclusive with approle)
    mount: transit
    key_name: authserver-signing
    timeout: 10s
    approle:
      role_id: ""
      secret_id: ""
      mount: approle
Env varField
AUTHPLANE_SIGNING_ALGORITHMsigning.algorithm
AUTHPLANE_SIGNING_KEY_STOREsigning.key_store
AUTHPLANE_SIGNING_KEY_PATHsigning.key_path
AUTHPLANE_SIGNING_PG_ENCRYPTION_KEY_ENVsigning.postgres_key.encryption_key_env (Postgres store only)
AUTHPLANE_VAULT_ADDRsigning.vault_transit.address
AUTHPLANE_VAULT_TOKENsigning.vault_transit.token
AUTHPLANE_VAULT_TRANSIT_MOUNTsigning.vault_transit.mount
AUTHPLANE_VAULT_TRANSIT_KEY_NAMEsigning.vault_transit.key_name
AUTHPLANE_VAULT_TIMEOUTsigning.vault_transit.timeout
AUTHPLANE_VAULT_APPROLE_ROLE_IDsigning.vault_transit.approle.role_id
AUTHPLANE_VAULT_APPROLE_SECRET_IDsigning.vault_transit.approle.secret_id
AUTHPLANE_VAULT_APPROLE_MOUNTsigning.vault_transit.approle.mount

Algorithms — ES256 (ECDSA P-256) is default: compact tokens, fast verification. RS256 (RSA 2048) for environments that require it (legacy IDPs, HSM constraints).

Key stores — keyfile writes PEM keys to key_path. postgres_key (HA-safe) stores keys in the storage DB with encryption at rest — required when running multiple instances so key rotation propagates via LISTEN/NOTIFY. Note: postgres (without _key) fails boot; the suffix disambiguates from the storage driver of the same name. vault_transit delegates signing to HashiCorp Vault Transit — private keys never leave Vault. See Security: Key management and Operate: Vault Transit.

Key rotation — authserver admin key rotate generates a new signing key. The old key stays in the JWKS until it expires so in-flight tokens keep verifying.


dcr — Dynamic Client Registration (RFC 7591)

dcr:
  mode: open                        # "open", "approved_redirects", or "admin_only"
  approved_redirects:               # Used when mode: approved_redirects
    - http://localhost:*
    - https://inspector.mcp.garden/*
  rate_limit: 10                    # Registrations per second
  rate_limit_burst: 20
  default_token_expiry: 15m
  default_refresh_expiry: 168h      # 7 days
Env varField
AUTHPLANE_DCR_MODEdcr.mode
AUTHPLANE_DCR_APPROVED_REDIRECTSdcr.approved_redirects (comma-separated)
AUTHPLANE_DCR_RATE_LIMITdcr.rate_limit
AUTHPLANE_DCR_RATE_LIMIT_BURSTdcr.rate_limit_burst
AUTHPLANE_DCR_DEFAULT_TOKEN_EXPIRYdcr.default_token_expiry
AUTHPLANE_DCR_DEFAULT_REFRESH_EXPIRYdcr.default_refresh_expiry

Modes:

  • open — any client can register with any redirect URI. Development only.
  • approved_redirects — anyone can register, but the redirect URI must match a pattern in approved_redirects. Balanced for production with well-known MCP clients.
  • admin_only — clients must be pre-registered via the Admin API or CLI. Hardened default for internal-only deployments.

Rate limits apply per-IP to the /oauth/register endpoint. Defaults are conservative — bump for automated CI environments.


cimd — Client ID Metadata Document

Auto-registration via URL client_id (draft-ietf-oauth-client-id-metadata-document).

cimd:
  enabled: true
  require_https: true               # Require HTTPS for CIMD document URLs
  cache_ttl: 1h                     # Cache fetched documents for this long
  fetch_timeout: 10s                # HTTP timeout for fetching CIMD docs
Env varField
AUTHPLANE_CIMD_ENABLEDcimd.enabled
AUTHPLANE_CIMD_REQUIRE_HTTPScimd.require_https
AUTHPLANE_CIMD_CACHE_TTLcimd.cache_ttl
AUTHPLANE_CIMD_FETCH_TIMEOUTcimd.fetch_timeout

When a client presents a URL as its client_id, AuthPlane fetches and validates the metadata document at that URL. Set require_https: false only for local dev with http:// CIMD URLs.


session

Cookie-based session for the login and consent pages.

session:
  cookie_name: authserver_session
  max_age: 24h
  secure: false                     # Must be true in production
  same_site: lax                    # "lax", "strict", or "none"
  secret: ""                        # Required in production (32+ bytes)
Env varField
AUTHPLANE_SESSION_COOKIE_NAMEsession.cookie_name
AUTHPLANE_SESSION_MAX_AGEsession.max_age
AUTHPLANE_SESSION_SECUREsession.secure
AUTHPLANE_SESSION_SAME_SITEsession.same_site
AUTHPLANE_SESSION_SECRETsession.secret

The session secret signs and encrypts session cookies. For localhost dev, a random ephemeral secret is generated if none is set — sessions don’t survive restart. For production, set a stable 32+ byte value (openssl rand -hex 32).


rate_limit

Global rate limiting + brute-force protection on auth endpoints.

rate_limit:
  enabled: true
  requests_per_second: 100          # Global RPS limit
  burst: 200                        # Burst allowance
  auth_fail_max: 10                 # Max failed auth attempts per IP
  auth_fail_window: 10m             # Window for counting failures
  auth_lockout: 15m                 # Lockout duration after exceeding max
Env varField
AUTHPLANE_RATE_LIMIT_ENABLEDrate_limit.enabled
AUTHPLANE_RATE_LIMIT_RPSrate_limit.requests_per_second
AUTHPLANE_RATE_LIMIT_BURSTrate_limit.burst
AUTHPLANE_RATE_LIMIT_AUTH_FAIL_MAXrate_limit.auth_fail_max
AUTHPLANE_RATE_LIMIT_AUTH_FAIL_WINDOWrate_limit.auth_fail_window
AUTHPLANE_RATE_LIMIT_AUTH_LOCKOUTrate_limit.auth_lockout

Token-bucket global limit plus per-IP tracking of failed logins. Lockout applies to the /login endpoint only — OAuth endpoints stay open.


oauth

oauth:
  require_scope: true               # Require scope parameter in authorize requests
Env varField
AUTHPLANE_OAUTH_REQUIRE_SCOPEoauth.require_scope

When true (default), /oauth/authorize requests without a scope parameter are rejected with invalid_scope per RFC 6749 §3.3. When false, missing scope defaults to all registered scopes for the target resource — useful for MCP clients (like Claude Code at v0.1.x) that don’t send scope on /authorize. The decision is recorded as ADR-012; the conformance rationale (both branches satisfy §3.3) lives in the authserver compliance statement.

Optional grants — on by default since v0.2.0

Defaults changed in v0.2.0 — four features flipped from false to true: client_credentials.enabled, dpop.enabled, token_exchange.enabled and xaa.enabled (the last is documented in its own section below). Set any of them to false — or the matching AUTHPLANE_*_ENABLED env var — to keep the v0.1.x posture. The full v0.2.0 breaking-change list is in the authserver CHANGELOG.

Client credentials, DPoP and token exchange were each off by default through v0.1.x and are on from v0.2.0. Set them to false to turn them off:

client_credentials:
  enabled: true                     # RFC 6749 §4.4 — false through v0.1.x
  token_expiry: 1h

dpop:
  enabled: true                     # RFC 9449 — false through v0.1.x
  nonce_ttl: 60s
  proof_lifetime: 60s
  require_nonce: false

token_exchange:
  enabled: true                     # RFC 8693 — false through v0.1.x
  allow_self_exchange: false
  max_chain_depth: 5
  token_expiry: 1h

dpop.enabled turns on DPoP support, not enforcement: a client that sends no proof still receives a bearer token. Rejecting unbound requests is the SDK’s inbound_dpop setting — see Enable DPoP end-to-end.

Env varFieldDefault
AUTHPLANE_CLIENT_CREDENTIALS_ENABLEDclient_credentials.enabledtrue (false through v0.1.x)
AUTHPLANE_CLIENT_CREDENTIALS_TOKEN_EXPIRYclient_credentials.token_expiry1h
AUTHPLANE_DPOP_ENABLEDdpop.enabledtrue (false through v0.1.x)
AUTHPLANE_DPOP_NONCE_TTLdpop.nonce_ttl60s
AUTHPLANE_DPOP_PROOF_LIFETIMEdpop.proof_lifetime60s
AUTHPLANE_DPOP_REQUIRE_NONCEdpop.require_noncefalse
AUTHPLANE_TOKEN_EXCHANGE_ENABLEDtoken_exchange.enabledtrue (false through v0.1.x)
AUTHPLANE_TOKEN_EXCHANGE_ALLOW_SELF_EXCHANGEtoken_exchange.allow_self_exchangefalse
AUTHPLANE_TOKEN_EXCHANGE_MAX_CHAIN_DEPTHtoken_exchange.max_chain_depth5
AUTHPLANE_TOKEN_EXCHANGE_TOKEN_EXPIRYtoken_exchange.token_expiry1h
  • client_credentials — required for machine-to-machine flows and any SDK that performs server-to-server introspection. See Concepts: Grants & flows and Topologies: Backend service + MCP.
  • dpop — sender-constrained tokens. Additive: clients that don’t send a DPoP header still get bearer tokens (unless require_nonce: true locks it down). See Concepts: DPoP.
  • token_exchange — powers delegation, Broker resources, and Cross-App Access. Cross-client authorization is enforced per-resource via policy.exchange.allowed_client_ids on each registered resource. See Concepts: Delegation & act-chain.

XAA and JWT Bearer

Enterprise-Managed Auth (Cross-App Access). On by default since v0.2.0 — the grant validates nothing until an operator registers a trusted IdP, because the registry starts empty. Disabled by default through v0.1.x, where the block is also YAML-only (no env-var overrides there).

xaa:
  enabled: true                     # false through v0.1.x
  jwks_cache_ttl: 1h
  token_expiry: 1h
  max_assertion_age: 5m
  subject_mode: auto_map            # "auto_map" or "strict"
  require_resource: false           # true = refuse exchanges that name no `resource=`

Since v0.2.0 every field also has an env var: AUTHPLANE_XAA_ENABLED, AUTHPLANE_XAA_TOKEN_EXPIRY, AUTHPLANE_XAA_MAX_ASSERTION_AGE, AUTHPLANE_XAA_REQUIRE_RESOURCE, AUTHPLANE_XAA_SUBJECT_MODE, AUTHPLANE_XAA_JWKS_CACHE_TTL.

Trusted IdPs, policies, and subject mappings are managed via the Admin REST API only — POST /admin/idps, POST /admin/xaa/policies, POST /admin/xaa/subject-mappings. There is no authserver admin xaa … CLI subcommand. See Concepts: Cross-App Access and Guides: Enterprise-Managed Auth.

agents

agents:
  enable_jwks_listing: false        # Publish agent JWK sets on /.well-known/agents.json
Env varField
AUTHPLANE_AGENTS_ENABLE_JWKS_LISTINGagents.enable_jwks_listing

Agent identity claims (agent_id, agent_chain) are emitted on every token by default. This flag controls the optional publishing of agent JWK sets for third-party verification.


admin

Admin API + Admin UI, on a separate port from public OAuth.

admin:
  enabled: true
  address: ":9001"                  # Separate port
  api_key: ""                       # Required in production
Env varField
AUTHPLANE_ADMIN_ENABLEDadmin.enabled
AUTHPLANE_ADMIN_ADDRESSadmin.address
AUTHPLANE_ADMIN_API_KEYadmin.api_key

Hosts both the REST API under /admin/* and the built-in React Admin UI at /admin/ui/. Authenticate API requests with Authorization: Bearer <api_key>. The UI uses the same key entered in-browser and stored in sessionStorage. Never expose :9001 to the public internet — keep it on an internal network or behind a firewall/NetworkPolicy.


oidc — Upstream OIDC Federation

oidc:
  enabled: false
  issuer: https://accounts.google.com
  client_id: ""
  client_secret: ""
  display_name: Google              # Button text on login page
  scopes: [openid, email, profile]
  redirect_uri: https://auth.example.com/oidc/callback
  show_local_login: true            # Show password form alongside OIDC button
  include_groups_scope: true        # Include "groups" scope if upstream supports it
  connector_id: ""                  # Dex connector_id parameter (optional)
Env varField
AUTHPLANE_OIDC_ENABLEDoidc.enabled
AUTHPLANE_OIDC_ISSUERoidc.issuer
AUTHPLANE_OIDC_CLIENT_IDoidc.client_id
AUTHPLANE_OIDC_CLIENT_SECREToidc.client_secret
AUTHPLANE_OIDC_DISPLAY_NAMEoidc.display_name
AUTHPLANE_OIDC_SCOPESoidc.scopes (comma-separated)
AUTHPLANE_OIDC_REDIRECT_URIoidc.redirect_uri
AUTHPLANE_OIDC_SHOW_LOCAL_LOGINoidc.show_local_login
AUTHPLANE_OIDC_INCLUDE_GROUPS_SCOPEoidc.include_groups_scope
AUTHPLANE_OIDC_CONNECTOR_IDoidc.connector_id

Validation: oidc.issuer and oidc.redirect_uri must use HTTPS in production; client_id, client_secret, and redirect_uri are required when enabled. See Guides: Federate to your IdP.


resources

Every MCP server (Mint) and every upstream provider target (Broker) is a resources entry.

resources:
  - slug: my-mcp-server
    uri: http://mcp-server:3000/mcp   # MCP canonical form — no trailing slash
    backend_kind: mint
    display_name: My MCP Server
    scopes:
      - name: tools/echo
        description: Echo a message
      - name: tools/query_database
        description: Query the database
    policy:
      exchange:
        allowed_client_ids: []        # Empty = any consented client
      runtime:
        client_ids: []                # Empty = no runtime binding
      connect:
        allowed_return_urls: []       # Overrides connect.allowed_return_urls
Env varField
AUTHPLANE_RESOURCE_URISingle Mint resource URI; slug auto-derived from host
AUTHPLANE_RESOURCE_SCOPESComma-separated scope names

Env vars support a single Mint resource. For multiple resources or any Broker, use YAML or the Admin API.

uri must match byte-for-byte what your MCP server publishes in its Protected Resource Metadata (RFC 9728). Compliant MCP clients echo the PRM resource field verbatim on /oauth/authorize and /oauth/token; AuthPlane uses exact string matching for audience validation. A one-character mismatch silently rejects every token. Use the MCP spec canonical form (no trailing slash, lowercase host, no default port). Watch for scheme/host case, default ports, percent-encoding — see Guides: Connect an MCP client.

backend_kind — mint (AS issues its own JWT) or broker (AS vends an upstream provider token via RFC 8693). See Concepts: Architecture.

policy.exchange.allowed_client_ids — per-resource replacement for the legacy global cross-client allowlist. Empty means any consented client can exchange into this resource.

policy.runtime.client_ids — per-resource runtime binding: whitelist of clients whose tokens are accepted at THIS resource. See Guides: Runtime client binding.


observability

Logs, traces, metrics.

observability:
  logging:
    level: info                     # debug | info | warn | error
    format: json                    # json | text
    add_source: false               # Include file:line in log records
    outputs:
      stdout: true
      otel: false
      otel_endpoint: ""             # OTLP gRPC endpoint
      insecure: false               # Allow plaintext gRPC
  tracing:
    enabled: false
    endpoint: ""                    # OTLP gRPC endpoint (e.g. localhost:4317)
    insecure: false
    sample_rate: 1.0                # 0.0 to 1.0
  metrics:
    provider: prometheus            # prometheus | otel | both | none
    path: /metrics                  # Prometheus scrape endpoint
    otel_endpoint: ""               # OTLP endpoint when provider=otel or both
    insecure: false
Env varField
AUTHPLANE_LOG_LEVELobservability.logging.level
AUTHPLANE_LOG_FORMATobservability.logging.format
AUTHPLANE_LOG_ADD_SOURCEobservability.logging.add_source
AUTHPLANE_LOG_STDOUTobservability.logging.outputs.stdout
AUTHPLANE_LOG_OTELobservability.logging.outputs.otel
AUTHPLANE_LOG_OTEL_ENDPOINTobservability.logging.outputs.otel_endpoint
AUTHPLANE_LOG_OTEL_INSECUREobservability.logging.outputs.insecure
AUTHPLANE_TRACING_ENABLEDobservability.tracing.enabled
AUTHPLANE_TRACING_ENDPOINTobservability.tracing.endpoint
AUTHPLANE_TRACING_INSECUREobservability.tracing.insecure
AUTHPLANE_TRACING_SAMPLE_RATEobservability.tracing.sample_rate
AUTHPLANE_METRICS_PROVIDERobservability.metrics.provider
AUTHPLANE_METRICS_PATHobservability.metrics.path
AUTHPLANE_METRICS_OTEL_ENDPOINTobservability.metrics.otel_endpoint
AUTHPLANE_METRICS_INSECUREobservability.metrics.insecure

Full metric catalog and Prometheus setup in Guides: Monitoring and Reference: Metrics & CLI.


data_encryption

Encrypts sensitive data at rest — upstream refresh grants in broker_grants, and (when Postgres key store is enabled) signing keys.

data_encryption:
  driver: aes_master                              # "aes_master" or "vault_transit_encrypt"
  aes_master:
    key_env: AUTHPLANE_VAULT_ENCRYPTION_KEY       # Env var containing hex-encoded 256-bit key
    old_key_env: AUTHPLANE_VAULT_OLD_KEY          # Previous key for rotation (optional)
  vault_transit_encrypt:
    address: https://vault:8200
    auth_method: token                            # "token" or "approle"
    token_env: VAULT_TOKEN
    mount_path: transit
    key_name: authserver-data
    approle:
      role_id_env: VAULT_APPROLE_ROLE_ID
      secret_id_env: VAULT_APPROLE_SECRET_ID
Env varField
AUTHPLANE_DATA_ENCRYPTION_DRIVERdata_encryption.driver
AUTHPLANE_DATA_ENCRYPTION_KEY_ENVdata_encryption.aes_master.key_env
AUTHPLANE_DATA_ENCRYPTION_VAULT_ADDRESSdata_encryption.vault_transit_encrypt.address
AUTHPLANE_DATA_ENCRYPTION_VAULT_AUTH_METHODdata_encryption.vault_transit_encrypt.auth_method
AUTHPLANE_DATA_ENCRYPTION_VAULT_TOKEN_ENVdata_encryption.vault_transit_encrypt.token_env
AUTHPLANE_DATA_ENCRYPTION_VAULT_MOUNT_PATHdata_encryption.vault_transit_encrypt.mount_path
AUTHPLANE_DATA_ENCRYPTION_VAULT_KEY_NAMEdata_encryption.vault_transit_encrypt.key_name
AUTHPLANE_DATA_ENCRYPTION_VAULT_ROLE_ID_ENVdata_encryption.vault_transit_encrypt.approle.role_id_env
AUTHPLANE_DATA_ENCRYPTION_VAULT_SECRET_ID_ENVdata_encryption.vault_transit_encrypt.approle.secret_id_env
AUTHPLANE_VAULT_ENCRYPTION_KEYActual AES-256-GCM key (64-char hex string, referenced by key_env)
AUTHPLANE_VAULT_OLD_KEYPrevious AES key during rotation (referenced by old_key_env)

AES master key — local AES-256-GCM with HKDF-derived per-purpose subkeys. Generate: openssl rand -hex 32.

Vault Transit encrypt — delegates encryption to HashiCorp Vault Transit; plaintext never touches disk. Required for compliance environments.


broker_providers

Upstream OAuth / API-key / service-account providers that Broker resources reference. See Concepts: Token Vault.

broker_providers:
  - slug: github
    display_name: GitHub
    protocol: oauth
    config_data:
      client_id: "your-github-app-client-id"
      client_secret_ref: "CONNECTOR_GITHUB_SECRET"
      authorize_url: "https://github.com/login/oauth/authorize"
      token_url:     "https://github.com/login/oauth/access_token"
      response_format: standard
  - slug: slack
    display_name: Slack
    protocol: oauth
    config_data:
      client_id: "your-slack-app-client-id"
      client_secret_ref: "CONNECTOR_SLACK_SECRET"
      authorize_url: "https://slack.com/oauth/v2/authorize"
      token_url:     "https://slack.com/api/oauth.v2.access"
      response_format: form

No env-var shortcut — broker providers are managed via YAML or the Admin API/CLI (authserver admin provider create). The slug is the public ID; Broker resources reference it via broker_provider_slug.

protocol — one of oauth, apikey, service_account. Each dispatches to a different adapter under internal/brokerproto/.

Default scopes live on the Broker Resource row, not the provider — one provider can back multiple resources with different scope sets.


connect

The user-facing OAuth flow to grant your MCP server upstream access.

connect:
  state_secret: "generate-a-32-byte-random-string"   # HMAC key for state tokens
  allowed_return_urls:                                # Global default (per-resource override available)
    - http://localhost:*
    - https://myapp.example.com/*
  redirect_base_url: http://localhost:9000            # Base URL for OAuth callbacks
Env varField
AUTHPLANE_CONNECT_STATE_SECRETconnect.state_secret
AUTHPLANE_CONNECT_ALLOWED_RETURN_URLSconnect.allowed_return_urls (comma-separated)
AUTHPLANE_CONNECT_REDIRECT_BASE_URLconnect.redirect_base_url

state_secret — HMAC key for signing state tokens during the connect flow. Must be at least 32 characters (openssl rand -hex 32). Different key from session.secret.

allowed_return_urls — global default whitelist for connect-flow return URLs. Each resource can override via resources[].policy.connect.allowed_return_urls for finer control.


Validation rules enforced at boot

Boot fails with a structured error if any of these are violated:

  • session.secret required when server.issuer is not localhost
  • session.secure: true required when server.issuer is not localhost
  • admin.api_key required when admin.enabled: true and server.issuer is not localhost
  • oidc.client_id + client_secret + redirect_uri all required when oidc.enabled: true
  • oidc.issuer + oidc.redirect_uri must use HTTPS in production
  • Every enabled feature’s required-config combination is validated up-front (partial config → boot fails at startup, not at first request)
  • resources[].uri must be a well-formed URL

Config precedence in practice

Debug your effective config by comparing all three sources:

# See defaults + YAML
$ authserver serve --config config.yaml --dry-run

# See what env vars are set
$ env | grep AUTHPLANE_

# See the final self-check report in the boot log
$ authserver serve --config config.yaml 2>&1 | grep -A20 "feature self-check"

Every enabled feature logs a one-liner at boot indicating what config it picked up. Misconfigurations are fatal — the process exits with a structured error naming the missing keys.