The dashboard signs nobody in and decides nothing about access: an auth proxy in front of it signs users in, the BFF passes each user's token on, and the OpenShell gateway decides. The install guide sets this up with Keycloak.
browser
│ HTTPS, session cookie
▼
oauth2-proxy ── signs users in at ──▶ Keycloak
│ X-Forwarded-Access-Token: <access token>
▼
BFF
│ gRPC, authorization: Bearer <access token>
▼
OpenShell gateway ── checks the token against the issuer's keys
| Part | What it does |
|---|---|
| Keycloak | Holds users, groups and roles; issues the tokens |
| oauth2-proxy | The auth proxy, which the Helm chart runs as a sidecar. It runs the sign-in, keeps the session in a cookie, refreshes the token and adds the access token to every request it passes on |
| BFF | Takes the token from x-forwarded-access-token, or else from Authorization: Bearer, and sends it with every gateway call; with no token it answers 401. It validates nothing and authorizes nothing |
| Gateway | Validates the token, and enforces the roles in it and workspace membership |
The proxy forwards the access token, not the ID token (--pass-access-token=true, --pass-authorization-header=false): an ID token is addressed to the dashboard's own client, not to the gateway.
The auth proxy and the gateway use the same issuer. In the access token that issuer gives the dashboard's client:
issis the gateway's issuer.audcontains the gateway's audience.- The gateway's roles claim holds the gateway's admin role or user role.
subidentifies the user. Workspace memberships are keyed by it.
Never point the gateway's roles claim at aud: the audience says who a token is for, not what its user may do. Step 2 of the install guide checks a token before installing.
oidc.provider: keycloak-oidcmakes oauth2-proxy read Keycloak's roles from the access token, andoidc.allowedRolesthen admits only users with one of the listed roles (a client role is written<client>:<role>). That is a gate in front of the UI; the gateway still decides every call.oidc.codeChallengeMethod: S256goes with a client that requires PKCE.- No audience mapper is needed when the roles are client roles of the client that is the gateway's audience (
openshell-apiin the guide): Keycloak's built-inrolesclient scope carries Audience Resolve, which adds a client toaudwhen the token holds one of its roles.
Keep oidc.provider: oidc, the default, and leave oidc.allowedRoles unset: the chart accepts it only with keycloak-oidc. oidc.allowedGroups limits who reaches the UI, if the provider puts a groups claim in the token. The provider supplies:
- a confidential client for oauth2-proxy, with the callback
https://<dashboard host>/oauth2/callback; - an access token that meets the list above, as a JWT: the gateway checks it against the issuer's keys;
- an
emailclaim not marked unverified, or oauth2-proxy refuses the sign-in.
- The session is oauth2-proxy's cookie, Secure and HttpOnly. The BFF keeps none.
- oauth2-proxy refreshes the access token every
authProxy.cookie.refresh(default2m). Keep that below the provider's access-token lifetime, or the gateway refuses an expired token while the UI still looks signed in. - On a 401 the UI reloads the page once, so the auth proxy can sign the user in again.
- Signing out sends the browser to
LOGOUT_URL(dashboard.logoutURLin the chart, default/oauth2/sign_out?rd=/oauth2/sign_in), which ends the proxy's session. With providerkeycloak-oidcthe proxy also ends the Keycloak session; for other providers see Sign-out.
A request that reaches the BFF directly can present any header. The chart prevents that: the BFF listens on 127.0.0.1, the Service exposes the proxy's port only, and the NetworkPolicy admits traffic to that port only. Never publish the BFF's port, 8080.
- The BFF also accepts
Authorization: Bearer <access token>, for a script or a test that reaches it directly. A chart install has no such path: its oauth2-proxy takes no bearer tokens (--skip-jwt-bearer-tokens=false), so every request needs a browser session. AUTH_DISABLED=trueis for local development: the BFF acts as a made-updev-user, forwards no token and ignores any on the request.- A gateway can be set to accept users without a token. That too is for local development; on a shared gateway leave it off, as the gateway chart does by default.
The decisions are in ADR 0002.