Configuration
The configuration model
Section titled “The configuration model”authserver serve resolves configuration in three layers, highest priority last:
- Built-in defaults — sensible for localhost development.
- YAML file — passed via
--config /etc/authserver/config.yaml. - Environment variables — every YAML key has an
AUTHPLANE_*equivalent, evaluated after the YAML file and overriding matching keys.
Ship one YAML file per environment and inject secrets via env (docker -e, Kubernetes env:, systemd Environment=). Boot validation fails fast: any “required-when” slot left empty (e.g. a non-localhost issuer without a session secret) stops the server with an error naming the missing field.
The env vars that matter
Section titled “The env vars that matter”Exact names, YAML keys, and defaults from the generated reference. The full table — every variable with types and required-when rules — lives at docs/reference/env-vars.md.
| Env var | YAML key | Default | Purpose / example |
|---|---|---|---|
AUTHPLANE_SERVER_ISSUER | server.issuer | http://localhost:9000 | Public issuer URL — https://auth.example.com (HTTPS, no trailing slash) |
AUTHPLANE_SERVER_ADDRESS | server.address | :9000 | Public OAuth listener |
AUTHPLANE_ADMIN_ADDRESS | admin.address | :9001 | Admin API + UI + /metrics listener — keep internal |
AUTHPLANE_ADMIN_API_KEY | admin.api_key | — | Required when admin is enabled and issuer isn’t localhost. openssl rand -hex 32; min 16 chars, obvious values (changeme, dev-admin-key, …) rejected at boot |
AUTHPLANE_SESSION_SECRET | session.secret | — | Required when issuer isn’t localhost. openssl rand -hex 32 (≥ 32 bytes) |
AUTHPLANE_SESSION_SECURE | session.secure | false | Set true once TLS is live |
AUTHPLANE_SERVER_ALLOWED_ORIGINS | server.allowed_origins | — | CORS origins for browser-based MCP clients — empty disables CORS (startup WARN) |
AUTHPLANE_STORAGE_DRIVER | storage.driver | sqlite | sqlite (single instance) or postgres (HA) |
AUTHPLANE_STORAGE_POSTGRES_DSN | storage.postgres.dsn | — | Required when driver is postgres — postgres://authserver@db/authserver?sslmode=require |
AUTHPLANE_STORAGE_POSTGRES_MAX_CONNS | storage.postgres.max_conns | 25 | Tune against replica count × pool size |
AUTHPLANE_SIGNING_KEY_STORE | signing.key_store | keyfile | keyfile, postgres_key (multi-replica), or vault_transit |
AUTHPLANE_SIGNING_KEY_PATH | signing.key_path | data/keys | Key directory when store is keyfile — back it up |
AUTHPLANE_SIGNING_ALGORITHM | signing.algorithm | ES256 | JWT signing algorithm |
AUTHPLANE_VAULT_ADDR | signing.vault_transit.address | — | Required when key store is vault_transit — https://vault.vault.svc:8200 |
AUTHPLANE_DATA_ENCRYPTION_DRIVER | data_encryption.driver | — | aes_master or vault_transit_encrypt — encrypts refresh tokens, Token Vault broker grants, and postgres_key blobs at rest |
AUTHPLANE_DATA_ENCRYPTION_KEY_ENV | data_encryption.aes_master.key_env | — | Name of the env var holding the 64-hex AES master key |
AUTHPLANE_CLIENT_CREDENTIALS_ENABLED | client_credentials.enabled | false | Machine-to-machine / agent identity (RFC 6749 §4.4) |
AUTHPLANE_DPOP_ENABLED | dpop.enabled | false | Sender-constrained tokens (RFC 9449) |
AUTHPLANE_TOKEN_EXCHANGE_ENABLED | token_exchange.enabled | false | Delegation + Token Vault upstream vends (RFC 8693) |
AUTHPLANE_OIDC_ENABLED | oidc.enabled | — | Federate login to an upstream OIDC IdP (Google, Okta, Dex) |
AUTHPLANE_LOG_LEVEL | observability.logging.level | info | debug for dev stacks |
AUTHPLANE_LOG_FORMAT | observability.logging.format | json | Structured logs to stdout |
AUTHPLANE_TRACING_ENABLED | observability.tracing.enabled | false | OTLP trace export; endpoint via AUTHPLANE_TRACING_ENDPOINT |
AUTHPLANE_METRICS_PROVIDER | observability.metrics.provider | prometheus | prometheus, otel, or both |
AUTHPLANE_RATE_LIMIT_ENABLED | rate_limit.enabled | true | Token-bucket limiting — 100 rps (AUTHPLANE_RATE_LIMIT_RPS), burst 200 (AUTHPLANE_RATE_LIMIT_BURST) |
Feature flags
Section titled “Feature flags”Optional grants ship off to keep a fresh install minimal — with a flag off, POST /oauth/token returns unsupported_grant_type even if the client lists the grant. Every machine-to-machine MCP integration needs at least client_credentials:
| Flag | Env | Turn it on for |
|---|---|---|
client_credentials.enabled | AUTHPLANE_CLIENT_CREDENTIALS_ENABLED | Service-account / agent identity flows |
dpop.enabled | AUTHPLANE_DPOP_ENABLED | Sender-constrained tokens, once clients support it |
token_exchange.enabled | AUTHPLANE_TOKEN_EXCHANGE_ENABLED | Delegation, scope narrowing, Token Vault |
oidc.enabled | AUTHPLANE_OIDC_ENABLED | Upstream IdP login (requires oidc.issuer, oidc.client_id, oidc.client_secret, oidc.redirect_uri) |
xaa.enabled | YAML only | Cross-App Access — enterprise IdP-managed auth via ID-JAG assertions |
Secrets you must mint
Section titled “Secrets you must mint”| Secret | Required when | How |
|---|---|---|
session.secret (AUTHPLANE_SESSION_SECRET) | issuer is not localhost | openssl rand -hex 32 |
admin.api_key (AUTHPLANE_ADMIN_API_KEY) | admin enabled, issuer not localhost | openssl rand -hex 32 |
connect.state_secret | broker providers / Token Vault Connect flow | openssl rand -hex 32 (min 32 chars) |
AES master key (env named by data_encryption.aes_master.key_env) | data_encryption.driver: aes_master | openssl rand -hex 32 (exactly 64 hex chars = 256-bit) |
A working production YAML
Section titled “A working production YAML”server: issuer: https://auth.example.com # Must be HTTPS, no trailing slash shutdown_wait: 20s allowed_origins: ["https://app.example.com"] # Or browser MCP clients fail CORS
storage: driver: postgres # AUTHPLANE_STORAGE_DRIVER postgres: dsn: "" # Inject via AUTHPLANE_STORAGE_POSTGRES_DSN max_conns: 25 # Tune against replica count × pool size
signing: algorithm: ES256 key_store: postgres_key # Multi-replica safe; requires data_encryption postgres_key: encryption_key_env: AUTHPLANE_SIGNING_KEY_ENC
data_encryption: driver: aes_master aes_master: key_env: AUTHPLANE_DATA_ENC_KEY # Hex 64-char; mint with: openssl rand -hex 32
session: secure: true # Required for HTTPS issuer same_site: lax secret: "" # Inject via AUTHPLANE_SESSION_SECRET
admin: enabled: true address: "127.0.0.1:9001" # Loopback or internal-only api_key: "" # Inject via AUTHPLANE_ADMIN_API_KEY
client_credentials: enabled: true # Flip on for machine-to-machine
observability: logging: { level: info, format: json } metrics: { provider: prometheus, path: /metrics }Then run with secrets injected via env:
export AUTHPLANE_STORAGE_POSTGRES_DSN="postgres://authserver@db/authserver?sslmode=require"export AUTHPLANE_SESSION_SECRET=$(openssl rand -hex 32)export AUTHPLANE_ADMIN_API_KEY=$(openssl rand -hex 32)export AUTHPLANE_SIGNING_KEY_ENC=$(openssl rand -hex 32)export AUTHPLANE_DATA_ENC_KEY=$(openssl rand -hex 32)authserver serve --config /etc/authserver/config.yamlVerify the listeners and admin auth:
curl -fsS http://localhost:9000/health | jq .curl -fsS http://localhost:9000/.well-known/oauth-authorization-server | jq -r .issuercurl -fsS -H "Authorization: Bearer $AUTHPLANE_ADMIN_API_KEY" \ http://localhost:9001/admin/stats | jq .Next: Docker · Kubernetes · Observability