This repository runs the shared Keycloak identity server used by Spatial AI applications. It also contains the OpenTech DB realm and its Go backend-for-frontend (BFF), which keeps Keycloak tokens in Redis and gives the browser only opaque, HttpOnly session cookies.
Application browser -- same-origin auth path --> application auth/BFF
| |
tokens in Redis |
| v
+----> Keycloak
app realm
The important boundary is: the Keycloak server may be shared, but application
realms, clients, roles, secrets, redirect URIs, sessions, and BFF instances must
not be shared by default. This repository currently provisions only the
opentechdb realm and opentechdb-auth confidential client.
- Keycloak 26 with PostgreSQL persistence.
- The isolated
opentechdbrealm, its client, user profile, and roles. - The OpenTech Go auth service and Redis-backed server-side sessions.
- A local Compose stack for development.
- An nginx-fronted Compose stack for remote deployment, by IP or by DNS name.
The Go auth service is not yet application-neutral. Registration assigns the
OpenTech contributor role, and returned roles are restricted to contributor
and admin. Another application can share this Keycloak deployment, but must
use its own realm/client and either standard OIDC integration or a separately
configured/adapted BFF.
Prerequisites are Docker with Compose v2, GNU Make, and OpenSSL.
git clone https://github.com/THD-Spatial-AI/keycloak-auth.git
cd keycloak-auth
make installmake install creates a mode-600, gitignored .env.local, generates each
secret independently, and starts PostgreSQL, Redis, Keycloak, the realm
initializer, and the Go auth service. It never replaces an existing secret.
Local services:
| Service | URL |
|---|---|
| Keycloak | http://localhost:8080 |
| Admin Console | http://localhost:8080/admin/ |
| OpenTech auth API | http://localhost:8001/api |
| Health endpoint | http://localhost:8001/api/health |
The local admin username is admin; its generated password is in
.env.local. There is no default OpenTech application user.
Useful commands:
make dev # start or rebuild the local stack
make dev-check # verify .env.local matches the persisted database
make dev-reset CONFIRM=delete-local-keycloak-data # delete disposable local auth data
make dev-status # show container health/status
make dev-logs # follow Keycloak and auth-service logs
make dev-down # stop containers without deleting volumes
make test # run all Go testsmake dev performs the credential check before Keycloak starts. If an older
.env.local must be preserved, restore the complete file so its database,
administrator, and client secrets stay consistent. The guarded dev-reset
target deletes the local Keycloak database and Redis sessions only when the
exact confirmation value is supplied.
An application can pin this repository at keycloak/:
git submodule add https://github.com/THD-Spatial-AI/keycloak-auth.git keycloak
git submodule update --init --recursiveClone an application together with the pinned auth version using:
git clone --recurse-submodules <application-repository-url>For an existing clone:
git submodule update --init --recursiveThe parent application should run that update before invoking Compose. To upgrade deliberately, update and review the auth repository first, then commit the new submodule pointer in the application:
git -C keycloak fetch origin
git -C keycloak checkout <reviewed-commit-or-tag>
git add keycloakNever make a parent application track an unpinned remote branch at runtime.
Use one realm per application unless shared accounts and single sign-on are an explicit product requirement. For example:
https://auth.example.org/realms/opentechdb
https://auth.example.org/realms/enerplanet
https://auth.example.org/realms/storcito
Separate realms prevent accidental sharing of users, passwords, sessions, roles, identity providers, and client configuration. Even when applications intentionally share a realm, each application still needs its own client.
Create the realm in the Admin Console or add a reviewed realm export under
realm/ with an idempotent initialization step. Create an OpenID Connect
client with:
- a unique client ID;
- exact HTTPS redirect URIs and web origins (no production wildcards);
- Authorization Code flow;
- a confidential client and secret when a server/BFF performs the exchange;
- a public client with Authorization Code + PKCE when there is no trusted backend (never put a client secret in browser code);
- only the scopes and role claims the application actually consumes.
The standard endpoints are discovered from:
https://<auth-host>/realms/<realm>/.well-known/openid-configuration
Typical server-side settings are:
OIDC_ISSUER=https://<auth-host>/realms/<realm>
OIDC_CLIENT_ID=<application-client-id>
OIDC_CLIENT_SECRET=<server-only-secret>
OIDC_REDIRECT_URI=https://<application-host>/auth/callbackValidate the token signature using the discovery/JWKS metadata and validate at
least iss, aud/authorized party, exp, and authorization state/nonce as
appropriate. Map only explicitly allowed roles into application permissions.
The OpenTech pattern keeps access and refresh tokens out of JavaScript:
Browser /auth-api/* --> application reverse proxy --> Go auth /api/*
Application backend -------------------------------> /internal/validate-session
The browser stores only session_id (HttpOnly) and a CSRF cookie. The
application backend validates a session server-to-server with a different,
32-or-more-character AUTH_INTERNAL_SECRET sent as X-Internal-Auth.
For OpenTech DB the relevant application settings are:
VITE_AUTH_API_BASE_URL=/auth-api
AUTH_SERVICE_URL=https://<auth-host>
AUTH_REALM=opentechdb
AUTH_INTERNAL_SECRET=<same-value-as-the-OpenTech-auth-service>
OPENTECHDB_AUTH_UPSTREAM=https://<auth-host>The application proxy maps /auth-api/* to the auth server's /api/*. The
callback remains on the application origin:
https://<application-host>/auth-api/auth/callback
For another application, deploy a separate BFF endpoint and Redis namespace or
database, use a separate internal secret, and adapt the current OpenTech role
and registration policy. Do not route multiple apps to the existing
OpenTech-specific /api/* service.
Before enabling sign-in, verify:
- discovery metadata is reachable over HTTPS;
- the issuer exactly matches the application's configured realm;
- redirect URI and web origin are exact matches;
- the client secret exists only on trusted servers;
- logout revokes the server session and clears cookies;
- one realm's identity and roles are rejected by every other application;
/internal/*, PostgreSQL, and Redis are not publicly reachable.
The first command creates .env.vm, generates independent secrets, detects the
VM address, and then stops so the application-server addresses can be reviewed:
make prod
$EDITOR .env.vm
make prodThe default VM stack uses plain HTTP and is intended only for a trusted test
network. AUTH_APP_ENV=production creates Secure session cookies and therefore
requires an HTTPS application origin for browser login. Never expose the HTTP
variant directly to the public internet.
make prod verifies that .env.vm still authenticates to the persistent
PostgreSQL volume before starting Keycloak, Redis, the OpenTech Go auth service,
and nginx. Keep .env.vm backed up with the PostgreSQL volume: the database
password, Keycloak administrator password, and OpenTech client secret are one
persistent credential set.
make prod-logs
make prod-downPOSTGRES_PASSWORD affects only the first initialization of a PostgreSQL data
volume. Recreating .env.vm while retaining an older volume therefore cannot
change the database login, Keycloak administrator, or realm-client secrets.
make prod detects this condition before Keycloak enters a restart loop.
If the deployment contains users or realm configuration, restore the complete
original .env.vm from backup. Do not change only KEYCLOAK_DB_PASSWORD,
because the administrator and application-client secrets must match too.
For a new or disposable deployment, intentionally recreate the persistent data:
make prod-reset CONFIRM=delete-all-keycloak-dataThis deletes all Keycloak realms/users in the Compose PostgreSQL volume and all Redis sessions. It does not run without the exact confirmation value.
Production secrets belong in a secrets manager or an uncommitted file with restricted permissions. Back up the Keycloak PostgreSQL volume. Redis contains active sessions; losing it logs users out but does not delete Keycloak users.
Optional GitHub and ORCID providers are configured in the opentechdb realm
with aliases github and orcid. Their provider-side callback is Keycloak's
broker endpoint:
https://<auth-host>/realms/opentechdb/broker/<alias>/endpoint
Provider secrets stay in Keycloak and never enter the frontend repository.