This module is a Keycloak realm resource provider extension.
mvn clean packageCopy the built JAR from target/ into your Keycloak providers/ directory, then rebuild/start Keycloak:
cp target/Backend-for-Frontend-1.0-SNAPSHOT.jar /path/to/keycloak/providers/
kc.sh build
kc.sh startAfter deployment, the extension is available under:
POST /realms/{realm-name}/bff/login/startPOST /realms/{realm-name}/bff/login/otpGET /realms/{realm-name}/bff/mePOST /realms/{realm-name}/bff/logoutPOST /realms/{realm-name}/bff/backend/call
/login is still available as an alias for /login/start.
For browser-originated POST endpoints (/login, /login/start, /login/otp, /logout, /backend/call), the BFF validates
Origin/Referer against the configured CSRF policy.
{
"username": "user@example.com",
"password": "secret"
}Possible responses:
{"status":"ok", ...}withSet-Cookiewhen password-only login is accepted.{"status":"otp_required","loginId":"...", ...}when OTP step must be completed.
{
"loginId": "otp-challenge-id",
"password": "secret",
"otp": "123456"
}A successful response returns {"status":"ok", ...} and issues the BFF session cookie.
The OTP step always requires the password to be submitted again and does not persist plaintext passwords server-side.
GET /realms/{realm-name}/bff/me validates BFF_SESSION and returns user/session metadata.
POST /realms/{realm-name}/bff/backend/call
{
"method": "GET",
"path": "/orders?limit=10",
"headers": {
"Accept": "application/json"
}
}The BFF validates the BFF_SESSION, refreshes the Keycloak access token when needed, and forwards:
Authorization: Bearer <access_token>- a strict allow-list of caller-provided headers (for example
Accept,Content-Type,If-*, and tracing headers)
to the configured backend target.
The BFF now needs an OIDC client configuration to mint and refresh tokens for backend propagation.
Set these provider keys in your Keycloak server config file:
- local install:
<keycloak-home>/conf/keycloak.conf - container image:
/opt/keycloak/conf/keycloak.conf
Example keycloak.conf entries:
spi-realm-restapi-extension-bff-oidc-client-id=bff-client
spi-realm-restapi-extension-bff-oidc-client-secret=change-me
spi-realm-restapi-extension-bff-oidc-token-base-url=https://sso.example.com/
spi-realm-restapi-extension-bff-backend-base-url=https://api.example.com/
spi-realm-restapi-extension-bff-session-store-mode=single-use-object
spi-realm-restapi-extension-bff-session-ttl-seconds=28800
spi-realm-restapi-extension-bff-backend-timeout-seconds=10
spi-realm-restapi-extension-bff-max-in-memory-sessions=20000
spi-realm-restapi-extension-bff-csrf-allowed-origins=https://app.example.com,https://admin.example.comConfig keys:
oidc-client-id(required): confidential/public client used for password and refresh grants.oidc-client-secret(optional): required for confidential clients.oidc-token-base-url(required): absolute HTTPS base URL used for token and refresh POST requests. For local development, loopback HTTP URLs (http://localhost/...,http://127.0.0.1/...,http://[::1]/...) are also accepted.backend-base-url(required for/backend/call): base URL allow-list target for proxy calls.session-store-mode:single-use-object(default, distributed) orin-memory.session-ttl-seconds: max BFF session lifetime (default28800).backend-timeout-seconds: timeout for token/backend HTTP requests (default10).max-in-memory-sessions: cap used only forin-memorymode.csrf-allowed-origins: optional comma-separated origin allow-list for browser-originated POST endpoints (/login,/login/start,/login/otp,/logout,/backend/call); when omitted, strict same-origin is enforced.
Same-origin deployment (recommended default):
If your frontend is served from the same origin as Keycloak/BFF, omit csrf-allowed-origins.
Requests with Origin/Referer are accepted only when they match the Keycloak/BFF origin.
# No csrf-allowed-origins setting required
spi-realm-restapi-extension-bff-oidc-client-id=bff-client
spi-realm-restapi-extension-bff-oidc-token-base-url=https://sso.example.com/Cross-origin frontend deployment:
If your frontend is hosted on a different origin, set every allowed browser origin explicitly.
spi-realm-restapi-extension-bff-csrf-allowed-origins=https://app.example.com,https://admin.example.comUse origin values only (scheme://host[:port]), without paths or trailing route segments.
For local development, include exact dev origins, for example http://localhost:5173.
When Keycloak runs locally over HTTP, set oidc-token-base-url to a loopback URL:
spi-realm-restapi-extension-bff-oidc-token-base-url=http://localhost:8080/Only loopback HTTP hosts are accepted (localhost, 127.0.0.1, ::1). Non-loopback hosts still require HTTPS.
If oidc-token-base-url is missing or invalid, login and token exchange requests fail closed with a server misconfiguration error.
The downstream backend should validate Keycloak bearer tokens, not the BFF_SESSION cookie:
- JWT validation with Keycloak JWKS (
/realms/{realm}/protocol/openid-connect/certs), or - token introspection (
/realms/{realm}/protocol/openid-connect/token/introspect) for opaque/reference tokens.
A small Alpine.js test app is available in examples/frontend.
An import-ready demo client configuration is available at examples/client.json.
A Docker Compose setup that starts Keycloak + demo backend + demo frontend is available at examples/docker/docker-compose.yml.
Quick start:
cd examples/docker
docker compose up --buildManual frontend quick start:
cd examples/frontend
cp .env.example .env
npm install
npm run devSee examples/frontend/README.md for details.