OpenTech DB uses its own opentechdb realm in a shared Keycloak installation.
Supabase stores catalogue/workflow data and hashed personal-token metadata
only. GoTrue is disabled, there is no Supabase user table used by the
application, and no Supabase credential or route is shipped to the browser.
Rows store the immutable Keycloak subject as plain attribution data without a
foreign key to a database user.
The React application does not use keycloak-js. It keeps the existing
OpenTech sign-in page and calls a standalone Go authentication service copied
and adapted from Storcito-Wildfire. The service talks to the Keycloak container,
stores Keycloak tokens server-side in Redis, and gives the browser only an
opaque session_id HttpOnly cookie.
Browser -- same-origin /auth-api --> Go auth service --> opentechdb realm
| | |
| opaque HttpOnly session cookie | tokens in Redis | users + roles
+------------ FastAPI ------------+ |
internal validation --------------------+
One remote Keycloak server can host several isolated realms, for example:
opentechdbenerplanetstorcito
Users, passwords, sessions, clients, roles, and identity-provider configuration
are separate per realm. A user in enerplanet is not automatically a user in
opentechdb. Each application must also use its own confidential client secret
and its own Go session service/configuration. FastAPI rejects any identity whose
realm is not exactly opentechdb.
The authentication stack is pinned from the standalone
keycloak-auth repository as
the keycloak/ Git submodule. Local development uses
keycloak/compose.local.yml, with a local Keycloak and local Postgres/Redis.
Production can point the application at a shared remote Keycloak deployment
using keycloak/compose.yml.
The existing form submits to the Go service:
GET /auth-api/csrf-tokencreates a CSRF cookie/token pair.POST /auth-api/loginacceptsusername(either username or email) andpassword.- Go authenticates against the
opentechdbrealm. - Keycloak access/refresh tokens are stored in Redis.
- The browser receives only
session_id(HttpOnly) andcsrf_tokencookies.
Registration uses username, email, password, and password confirmation.
The realm user profile contains only username and email; full name and copied
Storcito organization/profile fields are not part of this realm.
The unchanged provider buttons start a Keycloak-brokered authorization-code
flow through the Go service. State and a PKCE verifier are single-use values
stored in Redis for five minutes; an HttpOnly state cookie binds the callback
to the browser that initiated the flow. Provider client secrets belong in the
opentechdb realm's Identity Providers configuration, never in React.
Create identity providers with aliases exactly github and orcid. Configure
their provider-side callback to Keycloak's broker endpoint:
https://<keycloak-host>/realms/opentechdb/broker/<alias>/endpoint
The OpenTech confidential client's callback is instead:
https://<opentech-app>/auth-api/auth/callback
The realm defines only application roles needed by OpenTech DB:
contributor— submit technology and time-series data.admin— review submissions and administer catalogue/scraper data; includescontributoras a composite role.
New realm users receive contributor. Grant admin in the Keycloak Admin
Console; there is no hardcoded admin password and no Supabase role promotion.
FastAPI does not parse browser JWTs. For a request containing session_id, it
calls the Go-only endpoint GET /internal/validate-session with the shared
AUTH_INTERNAL_SECRET. The Go service refreshes near-expiry Keycloak tokens and
returns a filtered identity containing only subject, username, email, realm, and
the contributor/admin roles.
Security controls include:
- opaque HttpOnly, Secure-in-production, SameSite cookies;
- CSRF double-submit tokens for Go auth POSTs;
- exact Origin checks for cookie-authenticated FastAPI writes;
- login rate limiting and account lockout;
- browser-bound, one-time Redis OAuth state and PKCE;
- bounded per-IP authentication rate-limiter state and Keycloak session revocation on logout;
- exact realm checking and filtered roles;
- no access/refresh token in React, browser storage, or URLs;
- a separate internal secret for FastAPI-to-Go validation.
Every signed-in realm user can manage personal API tokens on the OpenTech profile page. These are OpenTech API credentials, not Keycloak access tokens: FastAPI generates and consumes them, while Keycloak continues to own the user identity. The data table stores the immutable Keycloak subject as attribution; it does not create an application or PostgreSQL user.
The implementation follows the Storcito-Wildfire design:
- tokens contain 32 cryptographically random bytes and use the
otdb_prefix; - only the SHA-256 digest and a short display prefix are stored;
- the complete secret is returned once, immediately after generation;
- tokens default to 90 days, may be configured for 30/365 days or no expiry, and can be revoked from the profile;
- each user may have at most 10 active tokens;
readscope permits onlyGETandHEAD;fullpermits writes allowed by the token's non-admin role;- a personal token can never carry
admin, including when created by an administrator, and cannot generate or revoke other tokens; - cookie and bearer credentials cannot be combined on one request.
Token metadata and hashes live in the backend-only Supabase api_tokens table
created by migration 013. RLS has no browser policy and anon/authenticated
have no privileges. The service-role key remains in FastAPI only. Apply the
migration before enabling the profile feature.
Use a token only over HTTPS and pass it in the header, never in a URL:
curl \
-H 'Authorization: Bearer otdb_<complete-secret>' \
'https://otdb.th-deg.de/api/v1/submissions/mine'The contributor role stored with a token is a creation-time snapshot, matching Wildfire's personal-token model. Revoke a user's active tokens when removing their contributor access; the default expiry limits the lifetime of stale snapshots. Admin access is always excluded regardless of that snapshot.
make install
make backend
make frontendmake install starts both the Supabase data services and the complete local
Keycloak/Go-auth/PostgreSQL/Redis stack. Use make auth later to start or
rebuild only the authentication stack. Both commands fetch the auth submodule
at the exact revision pinned by OpenTech DB. For a manual checkout, use:
git submodule update --init --recursiveBoth make install and make auth verify that the generated local credentials
match an existing Keycloak PostgreSQL volume before starting Keycloak. If an
older environment file must be preserved, restore the complete file. If the
local realm/users are disposable, reset only the authentication data with:
make auth-reset CONFIRM=delete-local-keycloak-dataThis does not reset the separate Supabase database.
Local endpoints:
| Service | URL |
|---|---|
| OpenTech frontend | http://localhost:5173 |
| FastAPI | http://localhost:8000 |
| Go auth API | http://localhost:8001/api |
| Keycloak | http://localhost:8180 |
| Keycloak Admin Console | http://localhost:8180/admin/ |
make configure creates keycloak/.env.local, generates independent secrets,
and synchronizes only AUTH_INTERNAL_SECRET with the backend .env.
Both Compose variants use the application-neutral project name
spatialai-keycloak and stable container names:
spatialai-keycloak-postgresspatialai-keycloak-redisspatialai-keycloakspatialai-keycloak-initspatialai-keycloak-auth-servicespatialai-keycloak-caddy(remote stack only)
The infrastructure names are shared; opentechdb remains only the isolated
realm/client name used by this application.
There are two different kinds of credentials:
- Keycloak Admin Console: local username is
admin; the random password is stored asKEYCLOAK_ADMIN_PASSWORDin the gitignoredkeycloak/.env.local. Production reads both values fromkeycloak/.env. - OpenTech application login: no default user/password is seeded. Register
with the existing OpenTech form (username, email, and your chosen password),
or create a user inside the
opentechdbrealm. The login field accepts that username or email. Grant theadminrealm role only when needed.
Do not use admin/admin or commit either environment file.
On the Keycloak/auth server, use the standalone repository (or this
application's initialized keycloak/ submodule), copy .env.example to .env,
and set the production host/callback values before running. From the OpenTech DB
root:
cp keycloak/.env.example keycloak/.env
$EDITOR keycloak/.env
make -C keycloak prodOn the application server:
- set
OPENTECHDB_AUTH_UPSTREAM=https://<keycloak-host>for Nginx; - set
AUTH_SERVICE_URL=https://<keycloak-host>for FastAPI; - use the same 32+ character
AUTH_INTERNAL_SECRETon both servers; - keep
AUTH_REALM=opentechdb; - expose browser auth through the app's
/auth-apireverse proxy so the opaque cookie stays same-origin with OpenTech DB.
For stronger production isolation, allow /internal/* only from the application
server or a private network in addition to the shared-secret check.
The standalone repository's README documents how another application connects through its own realm/client and why it must not reuse the OpenTech-specific Go session service unchanged. See also the official Keycloak documentation for container deployment, realm import, and production configuration.