Configure OIDC Authentication¶
Ballet does not manage passwords. Humans authenticate with an external OpenID Connect provider; Ballet validates the provider’s access tokens (ADR-0006: OIDC Claims-Based RBAC for Humans, Scoped Run Tokens for Agents).
Authentication is off by default: without [oidc] issuer_url
everyone is the single local user, a platform admin, and Core only
listens on localhost ([oidc] (core)). Configure an
issuer as below when several people, organizations or roles share an
installation.
Requirements for the identity provider¶
A public client for the web UI using the authorization code flow with PKCE (S256).
Access tokens must contain the audience
ballet(or whatever[oidc] audienceis set to). In Keycloak, add an Audience protocol mapper to the client.Claims used for authorization, typically
groups, must be in the access token. In Keycloak, add a Group Membership mapper with Full group path off.emailandnameclaims are shown in the UI when present.
Configure Core¶
[oidc]
issuer_url = "https://idp.example.com/realms/ballet"
audience = "ballet"
or BALLET_CORE_OIDC_ISSUER_URL / BALLET_CORE_OIDC_AUDIENCE. See
[oidc] (core).
Core fetches the issuer’s discovery document at startup and refuses to start if the issuer is unreachable. Signing keys are cached and refreshed automatically when the provider rotates them.
Every route under /api/ requires Authorization: Bearer <access
token>; requests without a valid token get 401 with
WWW-Authenticate: Bearer realm="ballet". Operational endpoints
(/healthz, /readyz, /metrics) are not authenticated.
Check the configuration:
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/v1/me
returns the caller as Ballet sees it:
{"subject": "8751…", "email": "bob@acme.test", "name": "Bob Developer",
"groups": ["acme-devs"]}
Grant the first administrator¶
Authentication alone grants nothing; access comes from role bindings
(Security). Give the initial administrators
platform-admin through configuration:
[rbac]
bootstrap_platform_admins = ["groups:ballet-admins"]
They can then create organizations, projects and role bindings in the web
UI — Organizations (create, rename, add projects) and Access (grant
and remove role bindings) — or through the API, e.g. make the
acme-devs group engineers of organization acme:
curl -X POST localhost:8080/api/v1/role-bindings \
-H "Authorization: Bearer $ADMIN_TOKEN" -H 'Content-Type: application/json' \
-d '{"claim":"groups","value":"acme-devs","role":"engineer","scope":"organization:acme"}'
The UI shows administration controls only to users whose bindings allow them; Core enforces the same rules on every request.
Development identity provider¶
The repository contains a ready Keycloak setup:
make dev-keycloak # Keycloak on http://localhost:8180, realm "ballet"
BALLET_CORE_OIDC_ISSUER_URL=http://localhost:8180/realms/ballet \
BALLET_CORE_RBAC_BOOTSTRAP_PLATFORM_ADMINS=groups:ballet-admins \
go run ./core/cmd/core
curl -H "Authorization: Bearer $(scripts/dev-token.sh bob)" localhost:8080/api/v1/me
make dev-keycloak runs Keycloak’s Java distribution (Java 21+);
with Docker or Podman, docker compose -f deploy/dev/compose.yaml up -d
is equivalent. Test users (password = user name):
User |
Groups |
|---|---|
|
|
|
|
|
|
The realm is defined in deploy/dev/keycloak/ballet-realm.json.
Use localhost (not 127.0.0.1) everywhere: the development issuer
is http://localhost:8180/realms/ballet.