Operate the OAuth2 server modes
The broker exposes an OAuth2 authorization-server surface on end-user port 8000. The
surface contains /oauth2/authorize, /oauth2/token, /oauth2/jwks.json, and
/.well-known/oauth-authorization-server. The broker uses one mode. The mode decides
where authorization requests and tokens originate:
- proxy (default) — Forward authorization and token requests to an upstream OAuth2 server. The broker still serves its metadata and republishes upstream keys.
- local — The broker is an authorization server. It issues JWT access tokens with managed ES256 keys.
- hybrid — The broker uses both modes. Agent registration selects the mode.
This guide explains the configuration for each mode. It also explains signing-key management and CEL claims. See OAuth2 server modes for the conceptual model.
What you need
- A selected deployment mode. Use proxy when you use a corporate OAuth2 server. Use local when the broker issues agent tokens. Use hybrid when one deployment must serve both agent types.
- For proxy or hybrid mode, the upstream issuer, authorization endpoint, and token endpoint.
- For local or hybrid mode, an encryption backend. The broker encrypts signing-key private material. It does not start in these modes without encryption. See configure encryption.
- Access to the broker YAML configuration and the admin API on port 14000.
All modes require PKCE S256 at the authorization endpoint. /oauth2/authorize uses the
agent UUID (agent.id) as client_id. It does not use the upstream OAuth2 client ID.
Proxy mode
Set mode: proxy. Configure upstream endpoints in proxy. The broker validates the user
grant, then forwards authorization and token requests. It republishes upstream signing keys
through its JWKS. upstream_jwks_min_refresh and upstream_jwks_max_refresh limit key
refresh frequency.
oauth2_authorization_server:
mode: "proxy"
proxy:
upstream_issuer_uri: "https://auth.example.com"
upstream_authorize_endpoint: "https://auth.example.com/authorize"
upstream_token_endpoint: "https://auth.example.com/token"
upstream_timeout: 30s
upstream_jwks_min_refresh: "15m"
upstream_jwks_max_refresh: "1h"
supported_response_types:
- code
supported_grant_types:
- authorization_code
The broker's own public URL — used as the issuer in the discovery metadata — comes from
server.enduser.public_url, not from this section.
Local mode
Set mode: local. Configure token issuance in local. The broker issues JWT access tokens
in this mode. It does not use upstream endpoints. It supports client_credentials and
authorization_code with PKCE.
oauth2_authorization_server:
mode: "local"
local:
token_ttl: "1h"
token_claims_expression: '{"team": agent.display_name}'
signing_keys:
bootstrap_timeout: 90s
token_ttl— Token lifetime. Use a Go duration such as30m,1h, or1h30m.token_claims_expression— Optional CEL expression for custom claims. See Custom token claims with CEL. An empty value adds no custom claims.signing_keys.bootstrap_timeout— The time limit to create the first signing key.
Local mode needs an encryption backend so it can protect signing-key private material:
encryption:
memory:
raw_key: "base64-encoded-32-byte-key" # use aws_kms for production
Hybrid mode
Set mode: hybrid. Configure both proxy and local. Both configurations are required.
The broker selects the mode from agent registration. Agents with an upstream client ID use
the proxy path. Local agents and CIMD agents receive locally issued tokens.
oauth2_authorization_server:
mode: "hybrid"
proxy:
upstream_issuer_uri: "https://auth.example.com"
upstream_authorize_endpoint: "https://auth.example.com/authorize"
upstream_token_endpoint: "https://auth.example.com/token"
upstream_timeout: 30s
upstream_jwks_min_refresh: "15m"
upstream_jwks_max_refresh: "1h"
local:
token_ttl: "1h"
token_claims_expression: ""
signing_keys:
bootstrap_timeout: 90s
supported_response_types:
- code
supported_grant_types:
- authorization_code
- client_credentials
As with local mode, hybrid mode requires an encryption backend for signing-key material.
Manage signing keys
Local and hybrid modes use broker-managed ES256 keys. The broker encrypts the private
key material at rest. It publishes public keys at /oauth2/jwks.json. Use the admin API on
port 14000 to rotate keys. Every request includes the principal header from the proxy. The
proxy enforces administrator privilege. See
configure authentication.
Add a key
POST /api/oauth2-server/signing-keys creates a key. ES256 is the default and the only
supported algorithm.
curl -X POST https://broker.internal:14000/api/oauth2-server/signing-keys \
-H "X-Remote-User: admin@example.com" \
-H "Content-Type: application/json" \
-d '{"algorithm": "ES256"}'
The broker publishes a new key on the next JWKS request after storage commits and marks it
current. It begins signing at activates_at, 600 seconds after key preparation. Each JWKS
request checks the shared key-set revision; the 300-second HTTP cache lifetime plus the
30-second JWKS rebuild limit leaves 270 seconds of grace margin under healthy-service
conditions. This gives compliant caches time to refresh; it cannot force external
verifiers to fetch a key. The response contains kid, algorithm, is_current,
activates_at, and created_at.
Keys created before migration 033 have no stored public JWK until a JWKS rebuild derives it and its conditional backfill succeeds. Before that backfill, publication depends on decrypting the private key. After backfill, the public key remains available through a decryption outage; issuing new tokens still needs the private key unless a valid local signer is cached. Retire an old key only after its tokens expire.
List keys
GET /api/oauth2-server/signing-keys returns the keys, newest first, wrapped in an items
array.
curl https://broker.internal:14000/api/oauth2-server/signing-keys \
-H "X-Remote-User: admin@example.com"
Promote a key to current
PUT /api/oauth2-server/signing-keys/{kid}/current promotes an existing key. The previous
key is no longer current but remains valid for verification. Tokens that it signed remain
valid until they expire.
curl -X PUT https://broker.internal:14000/api/oauth2-server/signing-keys/{kid}/current \
-H "Content-Type: application/json" \
-H "X-Remote-User: admin@example.com"
Retire a key
DELETE /api/oauth2-server/signing-keys/{kid} marks a key as deleted. The broker does not
delete the final key or current key. These requests return 409 last_key or 409 current_key. Add or promote a replacement first.
curl -X DELETE https://broker.internal:14000/api/oauth2-server/signing-keys/{kid} \
-H "X-Remote-User: admin@example.com"
Add a new key. Wait for its activation period. Promote it to current. Retire the old key only after tokens that use it have expired.
After a successful deletion commits, two separate broker targets apply:
- Broker-local validation: the first validation request that starts on any replica after the commit must reject a token signed by the removed key. If the database-backed key-set revision cannot be read, validation fails closed rather than using cached keys.
- Public JWKS: the first direct
GET /oauth2/jwks.jsonthat starts on any replica after the commit must omit the removed key or return an error, never a stale successful set. In-flight requests that began before the commit are not covered by either target.
These targets require migration 033 and revision-aware code on every serving replica; mixed-version rolling deployments do not meet them.
Successful JWKS responses retain Cache-Control: public, max-age=300. A verifier that
cached a pre-deletion response may continue accepting the key until its own cache expires;
intermediary and noncompliant verifier caches are outside the broker's control. Do not use
the broker's removal targets as a deadline for third-party token rejection.
Manage CIMD client-authentication keys
CIMD confidential third-party services use a separate ES256 key domain. These keys sign outbound private_key_jwt assertions.
They do not sign broker access tokens. The CIMD key routes work in proxy, local, and hybrid modes.
Token-signing key routes remain unavailable in proxy mode.
Create the first key before you register a CIMD confidential service:
curl -X POST https://broker.internal:14000/api/cimd-client-keys \
-H "X-Remote-User: admin@example.com" \
-H "Content-Type: application/json" \
-d '{"algorithm":"ES256"}'
The first key is immediately usable. Later generated keys remain public during the activation grace period. Promote a key to make it usable immediately:
curl -X PUT https://broker.internal:14000/api/cimd-client-keys/{kid}/current \
-H "Content-Type: application/json" \
-H "X-Remote-User: admin@example.com"
List active keys with GET /api/cimd-client-keys. Remove only a non-current key with DELETE /api/cimd-client-keys/{kid}.
The broker rejects removal of the last, current, or effective-current key. The public CIMD JWK Set is at <client-id>/jwks.json.
/oauth2/jwks.json does not publish CIMD keys.
Increment 2 (issue #264): public services without keys
Client registration and token-endpoint authentication are separate service settings.
Set client_registration: cimd and token_endpoint_auth_method: none for a CIMD public service.
The broker assigns a hosted client identifier URL. This explicit mode works in proxy, local,
and hybrid modes. In proxy mode, the service uses that URL, not the proxy upstream credentials.
The broker serves public metadata at the client identifier URL without a CIMD client-authentication key.
The document declares token_endpoint_auth_method: none and omits jwks_uri and
token_endpoint_auth_signing_alg. A request to <client-id>/jwks.json returns the uniform
JSON 404 for an unavailable CIMD JWK Set. Public service authorization uses PKCE S256.
Code exchange and refresh send the client identifier URL without a client secret, client
assertion, or authorization header. The broker does not retry with another authentication
method after provider rejection.
An empty CIMD key set is valid at startup only when no CIMD confidential service exists.
A CIMD public service does not create or require a key. A CIMD confidential service still
requires a usable key in every server mode. The key rotation procedure remains unchanged.
After a switch between none and private_key_jwt, a provider can cache the previous
document for up to 300 seconds. See manage agents and services
for service registration and replacement.
Persisted third-party service reads
Startup checks that each CIMD client ID matches the configured public URL. A stored registration or authentication error identifies the service ID. The broker does not validate stored discovery.resource_url values during reads or startup. Thus, an older URL that becomes blocked does not stop service lists or replicas from starting.
New service writes reject unsafe discovery URLs. The guarded outbound client also rejects unsafe destinations. Update the affected service with a valid URL before you retry discovery.
Custom token claims with CEL
In local and hybrid modes, local.token_claims_expression can add claims to broker-issued
JWTs. The CEL expression must return a map of claim names to values. Three variables are
available:
| Variable | Type | Contents |
|---|---|---|
agent | struct | The agent the token is issued for (for example agent.display_name). |
principal | string | The authenticated user the token acts for. |
request | map | The token request context. |
oauth2_authorization_server:
local:
token_claims_expression: '{"team": agent.display_name}'
The broker sets the base OAuth2 claims, including iss, sub, and exp. This expression
cannot replace these claims. It adds claims beside them.
Discovery and JWKS
The broker publishes two public endpoints on the end-user port in every mode. Clients and gateways use these endpoints to discover the server and validate tokens:
GET /.well-known/oauth-authorization-server— RFC 8414 metadata. This includes the issuer, endpoints, grant types, andcode_challenge_methods_supported: [S256].GET /oauth2/jwks.json— The public JWK set. Proxy mode republishes upstream keys. Local and hybrid modes publish the ES256 keys that you manage.
Point agents and token-validating gateways to these endpoints. Do not hard-code key or URL values. This makes key rotation transparent.
Related
- OAuth2 server modes — the concept behind these modes.
- Manage agents and services — register the agents that authenticate against this server.
- End-user API reference — the authorize, token, JWKS, and discovery contracts.