Spring Boot service for issuing and consuming invitation links for Keycloak.
Administrators create invite links with limited lifetime, usage count, and predefined realm roles. Recipients redeem those links to get a Keycloak account, required-actions email, and assigned roles.
- Admin UI for creating, resending, revoking, and deleting invites
- Invite status tracking: active, expired, overused, or revoked
- Daily cleanup of expired invites after the configured retention period
- Public invite confirmation page with no
GETside effects - CSRF-protected redeem flow with one-time confirmation challenge
- Failure-safe Keycloak provisioning with compensating user deletion
- Localized UI and mail text via
APP_LOCALEand message bundles
- Java 25
- Bun for repository tooling
- Docker
- Docker Compose plugin
- prek
- Copy
.env.example.localto.env. - Replace placeholders, especially
KEYCLOAK_URL,KEYCLOAK_CLIENT_SECRET,INVITE_TOKEN_SECRET, andINVITE_PUBLIC_BASE_URL. - Run
make run-local. - Run
make testfor tests ormake checkfor the full lint/test/coverage gate.
GET /invite/{realm}/{token}validates the token and renders a confirmation page.POST /invite/{realm}/{token}redeems the invite, creates the user, assigns realm roles, sends the required-actions email, marks the invite as used, and redirects to/invite/success.POSTrequires a valid CSRF token and a one-time confirmation challenge from the latestGETpage in the current browser session.- Confirmation challenges expire after 10 minutes and are stored in the server-side HTTP session. For multi-instance deployments, use sticky sessions or shared session state.
- If provisioning fails after user creation, the service deletes the created Keycloak user. Permanent Keycloak/configuration errors revoke the invite; transient errors leave it retryable.
Configuration is externalized through environment variables using Spring relaxed binding. Prefer
.env files over editing application.yml; Docker Compose loads .env automatically.
Required values:
KEYCLOAK_URLKEYCLOAK_CLIENT_SECRETINVITE_PUBLIC_BASE_URLINVITE_TOKEN_SECRET
Useful defaults:
- Spring profile:
prod; local examples setSPRING_PROFILES_ACTIVE=local - Keycloak realm:
master - Keycloak client ID:
invites-keycloak - Required admin role:
invite-admin - App locale:
en(ruis also bundled) - PostgreSQL host/database/user/password:
db/invites-keycloak
Docker Compose accepts POSTGRES_DB, POSTGRES_USER, and POSTGRES_PASSWORD overrides for the
database container. Local app connections can override the default database host with
POSTGRES_HOSTNAME.
KEYCLOAK_URL must be the Keycloak base URL without /realms/{realm}, for example
https://sso.example.com or https://sso.example.com/auth.
invite.realms controls realms shown in the admin UI. Default roles can be set with env variables:
INVITE_REALMS_MASTER_ROLES_0=invite-admin
INVITE_REALMS_MASTER_ROLES_1=another-role
INVITE_REALMS_PARTNERS_ROLES_0=partner-userMail is enabled by setting SPRING_MAIL_HOST and related SPRING_MAIL_* variables. MAIL_FROM
sets the optional sender address. API docs and Swagger UI are disabled by default; enable them with:
SPRINGDOC_API_DOCS_ENABLED=true
SPRINGDOC_SWAGGER_UI_ENABLED=true- Use the
masterrealm or setKEYCLOAK_REALM. - Create a confidential client named
invites-keycloakor setKEYCLOAK_CLIENT_ID. - Enable Standard Flow and service account.
- Set redirect URI to
<app-base-url>/login/oauth2/code/keycloak. - Set Web Origins to
<app-base-url>. - Copy the client secret to
KEYCLOAK_CLIENT_SECRET. - Create realm role
invite-adminor setKEYCLOAK_REQUIRED_ROLE. - Grant
invite-adminto users who should access the admin UI. - Include realm roles in the ID token via the built-in
rolesclient scope or arealm_access.rolesmapper. - Grant the service account
manage-users,view-realm, andmanage-realmrealm-management roles.
Missing service-account roles result in 403 errors when listing roles or creating users.
prek installinstalls git hooks.make run-localstarts PostgreSQL with Docker Compose and runs Spring Boot.docker compose up -d db && ./gradlew bootRunis the faster dev loop after.envexists.make testruns tests.make lintruns formatting checks and Detekt.make lint-fixapplies formatting and Detekt fixes.make check-configvalidates the Docker Compose model without resolving environment values.make checkruns the full local gate.make check-fixruns the full gate with automatic fixes.- GitHub Dependabot alerts and security updates monitor dependency vulnerabilities.
make build-imagebuildshu553in/invites-keycloak:local.make run-dockerstarts the full Docker Compose stack.make stop-dockerstops it.
/redirects to/admin/invite./admin/invite/**is the authenticated admin UI./invite/{realm}/{token}is the public invite page and redeem endpoint./invite/successis the public success page after successful redemption./actuator/healthand/actuator/prometheusare public; other actuator endpoints require the admin role.
Reverse proxies must send Host, X-Forwarded-Proto, X-Forwarded-Port, and X-Forwarded-For
because the app uses forwarded headers for OAuth redirects.
CI publishes ghcr.io/hu553in/invites-keycloak:
latestand immutablesha-*tags on pushes tomain- git tag name on tags matching
v*
Release helpers run the full local gate before creating a tag:
make release-patch
make release-minor
make release-majorDeployment steps:
- Provision
.envwith Keycloak, invite, mail, and database settings. - Point
compose.yamlor an override file at the desired image tag. - Run:
docker compose pull && docker compose up -d --wait - Verify
/actuator/health.
See exact versions in gradle/libs.versions.toml and service wiring in compose.yaml.
- Java, Kotlin, Gradle, Spring Boot
- PostgreSQL, Flyway, Spring Data JPA
- Spring Security OAuth2 Client, Thymeleaf
- WebClient for Keycloak Admin REST API
- Micrometer, Prometheus, optional OTLP tracing
- Detekt, Kover, Testcontainers, WireMock