Session Bridge
Session bridge turns a Keycloak access token into an Endatix Hub session. A mobile app, or any other client that already holds a Keycloak access token, posts that token to Hub. Hub exchanges it with Keycloak and sets the Auth.js session cookie on the response.
Complete Keycloak setup first. The dev realm adds a public client mobile-app whose access tokens set aud to endatix-hub, which is what Keycloak requires of the subject token. Sign in as any realm user (admin@2), for example external-admin@endatix.com.
curl -s -X POST http://127.0.0.1:8080/realms/endatix/protocol/openid-connect/token \
-d 'grant_type=password' \
-d 'client_id=mobile-app' \
-d 'username=external-admin@endatix.com' \
-d 'password=admin@2'
The access_token from that response is the body field for POST /api/auth/session-bridge.
Hub then calls Keycloak as endatix-hub and asks for a refresh token with audience set to endatix-hub. Keycloak 26.8 rejects that request: requested_token_type unsupported for a refresh token, and Requested audience not available: endatix-hub when that audience is sent. An exchange that omits audience and asks for an access token returns id_token and access_token and no refresh_token. Hub still requires refresh_token before it writes the session cookie, so this realm does not make the Hub session-bridge call succeed on Keycloak 26.8. It does issue the subject token the call needs.
When the route is on
POST /api/auth/session-bridge is allowed when NODE_ENV is not production. In production it returns 403 with detail Session bridge is not allowed, unless FLAG_EXPERIMENTAL_FEATURES=true.
The page /session-bridge is a development form for pasting a token. It uses the same switch and redirects to /signin when the route is off.
What Hub sends to Keycloak
Hub calls ${AUTH_KEYCLOAK_ISSUER}/protocol/openid-connect/token as the endatix-hub client (AUTH_KEYCLOAK_CLIENT_ID and AUTH_KEYCLOAK_CLIENT_SECRET):
| Form field | Value |
|---|---|
grant_type | urn:ietf:params:oauth:grant-type:token-exchange |
subject_token | the access token from the request |
subject_token_type | urn:ietf:params:oauth:token-type:access_token |
requested_token_type | urn:ietf:params:oauth:token-type:refresh_token |
audience | endatix-hub |
scope | openid email profile |
Keycloak must return access_token, refresh_token, id_token, and expires_in. Hub reads sub, email, and name (or preferred_username) from the ID token, writes an Auth.js session cookie, and returns:
{
"success": true,
"user": {
"id": "keycloak-subject",
"name": "Test User",
"email": "external@endatix.com",
"image": null
}
}
The browser that should be signed in must receive that Set-Cookie. A mobile app that calls Hub from its own server does not sign in the user's browser unless it forwards the cookie to that browser.
Keycloak
The posted access token's aud claim must include endatix-hub. That is the client Hub uses to call the token endpoint. Keycloak rejects the exchange otherwise. See standard token exchange.
One way to set aud is an audience mapper on the client that issues the source token, with included client audience endatix-hub. The dev realm's endatix-hub client maps audience account for Hub login. A source client used only for the mobile token needs its own mapper aimed at endatix-hub.
Example source token:
{
"iss": "http://127.0.0.1:8080/realms/endatix",
"azp": "mobile-app",
"aud": ["endatix-hub"],
"scope": "openid email profile"
}
iss must match AUTH_KEYCLOAK_ISSUER. Use 127.0.0.1 when Keycloak is the local compose file.
Lightweight access tokens are not required for this call. Use them when you also follow Keycloak RBAC.
Call the endpoint
curl -i -X POST http://localhost:3000/api/auth/session-bridge \
-H 'Content-Type: application/json' \
-d '{"access_token":"<keycloak-access-token>"}'
The JSON field is access_token. A body that uses token returns 400 with detail Missing access_token. Please provide a valid access token.
Failures
Responses are problem details (type, title, detail, status).
- 403
Session bridge is not allowed. Production withoutFLAG_EXPERIMENTAL_FEATURES=true. - 400
Missing access_token. Please provide a valid access token.The body failed the request schema. - 500 title
Session bridge server error, detailMissing required auth provider. Please contact support.KeycloakAuthProvideris not registered. See Keycloak setup. - 400 detail
Token exchange failed,errorCodevalidation_error. Keycloak rejected the grant (HTTP 400 from the token endpoint). Check thataudincludesendatix-hub, the client secret matches, and standard token exchange is enabled. Keycloak'serror_descriptionis logged with the failure and is not copied intodetail. - 500 detail
Network error. Failed to connect to the Keycloak token exchange endpoint.Hub could not reach${AUTH_KEYCLOAK_ISSUER}/protocol/openid-connect/token. - 400
errorCodeEXCHANGED_TOKEN_INVALID, detailSession bridge token exchange failed. Insufficient information to establish a session.The exchange response was missing a field the session needs (id_token,refresh_token,expires_in, or a subject).fieldsnames the bad properties. - 400
errorCodeMISSING_ID_TOKENwhen the exchange response has no ID token.
Next steps
- Keycloak setup for the realm, issuer, and Hub session.
- Keycloak RBAC when exchanged tokens must carry Endatix roles.
- External authorization for how the API uses those roles.