A small OIDC callback broker for Keycloak that supports:
- Shared redirect callback endpoint (
/callback) - DB-backed allowed app configuration
- CRUD APIs for app management (for GUI use)
- One-time code exchange API for apps (
/v1/auth/exchange)
GET /healthzGET /start?app=<slug>&return_to=<path-or-url>GET /callbackPOST /v1/auth/exchangeGET /v1/appsPOST /v1/appsGET /v1/apps/{slug}PUT /v1/apps/{slug}DELETE /v1/apps/{slug}
Set ADMIN_API_TOKEN (or ADMIN_API_TOKEN_FILE) to require:
Authorization: Bearer <token>
for /v1/apps* endpoints.
curl -X POST http://localhost:8080/v1/apps \
-H 'Content-Type: application/json' \
-d '{
"slug": "shell",
"display_name": "Shell App",
"base_url": "https://shell.suncoast.systems",
"enabled": true
}'curl -i 'http://localhost:8080/start?app=shell&return_to=/auth/callback'curl -X POST http://localhost:8080/v1/auth/exchange \
-H 'Content-Type: application/json' \
-d '{"code":"<gateway_code>","app_slug":"shell"}'To keep default behavior unchanged, token shaping is opt-in at exchange time.
Request a token exchange for a specific audience:
curl -X POST http://localhost:8080/v1/auth/exchange \
-H 'Content-Type: application/json' \
-d '{
"code":"<gateway_code>",
"app_slug":"shell",
"requested_audience":"graphql-api-7603d234"
}'Request Hasura claims using the configured Hasura audience lookup:
curl -X POST http://localhost:8080/v1/auth/exchange \
-H 'Content-Type: application/json' \
-d '{
"code":"<gateway_code>",
"app_slug":"shell",
"request_hasura_claims": true
}'When request_hasura_claims=true and requested_audience is not provided, the audience is resolved in this order:
- Vault lookup (
HASURA_TOKEN_AUDIENCE_VAULT_PATH, if configured) - Fallback static value (
HASURA_TOKEN_AUDIENCEorHASURA_TOKEN_AUDIENCE_FILE)
Vault lookup supports {app_slug} in the path template. Example:
HASURA_TOKEN_AUDIENCE_VAULT_PATH=kv/data/keycloak/hasura/{app_slug}HASURA_TOKEN_AUDIENCE_VAULT_KEY=audience
EXTERNAL_BASE_URL(for examplehttps://login.suncoast.systems)KEYCLOAK_ISSUER(for examplehttps://auth.suncoast.systems/realms/external)OIDC_CLIENT_ID- Database config via
DATABASE_URLorDB_*vars
OIDC_CLIENT_SECRETorOIDC_CLIENT_SECRET_FILEADMIN_API_TOKENorADMIN_API_TOKEN_FILECORS_ALLOW_ORIGINS(comma-separated list or*)HASURA_TOKEN_AUDIENCEorHASURA_TOKEN_AUDIENCE_FILE(fallback whenrequest_hasura_claims=true)VAULT_ADDR(required whenHASURA_TOKEN_AUDIENCE_VAULT_PATHis set)VAULT_TOKENorVAULT_TOKEN_FILE(required whenHASURA_TOKEN_AUDIENCE_VAULT_PATHis set)HASURA_TOKEN_AUDIENCE_VAULT_PATH(Vault API path; can include{app_slug})HASURA_TOKEN_AUDIENCE_VAULT_KEY(field name in the Vault secret, defaultaudience)STATE_TTL(default10m)EXCHANGE_CODE_TTL(default2m)APP_CODE_PARAM(defaultgateway_code)CALLBACK_PATH(default/callback)
If not using DATABASE_URL:
DB_HOST(defaultyb-tserver-service.yugabyte.svc.cluster.local)DB_PORT(default5433)DB_NAME(defaultkeycloak)DB_SEARCH_PATH(defaultkeycloak)DB_SSLMODE(defaultdisable)DB_USERorDB_USER_FILEDB_PASSWORDorDB_PASSWORD_FILE
docker build -t keycloak-auth-gateway:local .This repo includes a GitHub Actions workflow at .github/workflows/publish-image.yml.
On pushes to the default branch (and on tags), it builds and publishes:
ghcr.io/<repo-owner>/keycloak-auth-gateway:latest(default branch)ghcr.io/<repo-owner>/keycloak-auth-gateway:sha-<commit>
You can also run the workflow manually with workflow_dispatch.