Keycloak login theme using helpwave hightide components, plus the Keycloak SPI extensions that power the registration flow (Cloudflare Turnstile + privacy acceptance) and the profile picture upload.
npm cinpm run lint
npm run typechecknpm run build-keycloak-themeThis will generate the theme jar files in dist_keycloak/.
Note: You need Maven installed to build the theme (Maven >= 3.1.1, Java >= 17).
- On macOS:
brew install maven - On Debian/Ubuntu:
sudo apt-get install maven - On Windows:
choco install openjdkandchoco install maven
The Java extensions live under keycloak-extensions/ and are built with Maven:
cd keycloak-extensions
mvn -DskipTests packageThis produces three jars:
turnstile-authenticator/target/helpwave-turnstile-authenticator-<v>.jarprivacy-acceptance/target/helpwave-privacy-acceptance-<v>.jarprofile-picture/target/helpwave-profile-picture-<v>.jar(shaded with AWS SDK + Thumbnailator)
Drop all three (alongside the theme jar) into Keycloak's providers/ directory and run
kc.sh build.
Start keycloak and postgres services:
docker compose upThis will:
- Start postgres database
- Start keycloak on port 8080
- Import realms from
keycloak/import/ - Mount the theme jar from
dist_keycloak/
Default admin credentials:
- Username:
admin - Password:
admin
After starting the services, you can access:
- Customer realm login: http://localhost:8080/realms/customer/protocol/openid-connect/auth?client_id=account-console&redirect_uri=http://localhost:8080/realms/customer/account/&response_type=code&scope=openid
- Team realm login: http://localhost:8080/realms/team/protocol/openid-connect/auth?client_id=account-console&redirect_uri=http://localhost:8080/realms/team/account/&response_type=code&scope=openid
- Keycloak admin console: http://localhost:8080/admin
For nixos users, see docs/nixos.md for nix-shell setup instructions.
- hightide component integration
- Realm indicator chip with deterministic color mapping
- Custom login, register, and forgot password pages
- Field-level validation matching hightide patterns
- Cloudflare Turnstile CAPTCHA on signup (
helpwave-turnstileFormAction SPI) - Privacy policy checkbox on signup with acceptance metadata stored on the user
(
helpwave-privacy-acceptanceFormAction SPI) - Profile picture upload with server-side scaling to multiple sizes and storage in any
S3-compatible bucket (
helpwave-pictureRealm Resource SPI)
The release workflow publishes the following jars on every version bump in package.json:
| Jar | Purpose |
|---|---|
keycloak-theme-for-kc-26.2-and-above.jar |
The login/account theme |
helpwave-turnstile-authenticator-<v>.jar |
Cloudflare Turnstile registration form action |
helpwave-privacy-acceptance-<v>.jar |
Privacy acceptance form action + attribute store |
helpwave-profile-picture-<v>.jar |
Profile picture REST endpoint + R2/S3 upload |
Copy all jars into Keycloak's providers/ directory (or mount them into the container)
and run kc.sh build to rebuild the runtime, then start Keycloak normally.
- Open the Keycloak admin console.
- Go to Authentication → Flows and duplicate the built-in registration flow.
- In your new copy, add two executions to the registration form:
Cloudflare Turnstile (helpwave)— set to RequiredPrivacy Policy Acceptance (helpwave)— set to Required
- Click the gear on each execution to configure it:
- Turnstile: set the
Turnstile site key(public) andTurnstile secret(private). Get these from https://dash.cloudflare.com/?to=/:account/turnstile. - Privacy: set the
Privacy policy URL(defaults tohttps://helpwave.de/privacy) and an optionalPrivacy policy versionstring. Both are persisted on the user asprivacy_policy_accepted_atandprivacy_policy_versionattributes.
- Turnstile: set the
- Set this flow as the realm's Registration flow binding.
The profile picture SPI accepts standard AWS S3 or Cloudflare R2 (any S3-compatible backend). It exposes itself at:
/realms/{realm}/helpwave-picture
POST the raw image bytes (Content-Type: image/jpeg|png|webp, or multipart/form-data
from a <input type="file">) with a Bearer access token. The endpoint scales the image to
512/256/128/64 px JPEGs and writes them to the bucket. The primary URL is stored on the
user as the standard OpenID Connect picture attribute; thumbnail URLs as
picture_thumb_64|128|256. DELETE removes both the bucket objects and the attributes.
Configuration is read from Keycloak SPI settings (preferred — see
docs/deployment-nixos.md for _secret integration) or
environment variables as a fallback:
SPI key (keycloak.conf / NixOS services.keycloak.settings) |
Env var fallback | Required |
|---|---|---|
spi-realm-restapi-extension-helpwave-picture-endpoint |
HELPWAVE_PICTURE_ENDPOINT |
R2 only |
spi-realm-restapi-extension-helpwave-picture-region (def auto) |
HELPWAVE_PICTURE_REGION |
no |
spi-realm-restapi-extension-helpwave-picture-bucket |
HELPWAVE_PICTURE_BUCKET |
yes |
spi-realm-restapi-extension-helpwave-picture-access-key |
HELPWAVE_PICTURE_ACCESS_KEY |
yes |
spi-realm-restapi-extension-helpwave-picture-secret-key |
HELPWAVE_PICTURE_SECRET_KEY |
yes |
spi-realm-restapi-extension-helpwave-picture-public-base-url |
HELPWAVE_PICTURE_PUBLIC_BASE_URL |
yes |
spi-realm-restapi-extension-helpwave-picture-max-bytes (def 5 MiB) |
HELPWAVE_PICTURE_MAX_BYTES |
no |
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_REGION=auto
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_BUCKET=helpwave-id-avatars
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_ACCESS_KEY=...
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_SECRET_KEY=...
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_PUBLIC_BASE_URL=https://cdn.helpwave.de/avatarsKC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_REGION=eu-central-1
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_BUCKET=helpwave-id-avatars
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_ACCESS_KEY=...
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_SECRET_KEY=...
KC_SPI_REALM_RESTAPI_EXTENSION_HELPWAVE_PICTURE_PUBLIC_BASE_URL=https://avatars.helpwave.deTwo Keycloakify env vars expose the SPI to the theme at render time:
| Env var | Purpose |
|---|---|
TURNSTILE_SITE_KEY |
Optional fallback when the Turnstile authenticator config is not yet bound |
PROFILE_PICTURE_API_URL |
Full URL to /realms/<realm>/helpwave-picture |
Set them in the Keycloak container, e.g.:
KC_TURNSTILE_SITE_KEY=0x4AAAAAAA...
KC_PROFILE_PICTURE_API_URL=https://id.helpwave.de/realms/customer/helpwave-picture(Keycloakify reads KC_<NAME> and exposes it as kcContext.properties.<NAME>.)
A complete services.keycloak example with _secret file handling and the matching
admin-console steps lives in docs/deployment-nixos.md.
Bump version in package.json on main. The CI workflow builds the theme + SPIs and
publishes a GitHub release with all four jars attached.