Self-managed OIDC
Point a Sentisec install you run yourself at your own OIDC provider โ realm setup, the workspace claim, role mapping, and what the mode does not cover.
A Sentisec install you run yourself can authenticate directly against your own OIDC provider. No Sentisec-operated service sits anywhere in the sign-in path โ which is the point, for sovereignty requirements and disconnected environments.
This page is written against Keycloak, because that is what such deployments usually already run. Nothing in it is Keycloak-specific beyond the console paths, so it applies unchanged to Zitadel, Authentik, Ping, or any compliant provider.
How it works
- The dashboard runs a standard authorization-code flow with PKCE against your realm, discovering every endpoint from the issuer. Nothing about your provider is hardcoded.
- The resulting session lives in an encrypted cookie in the operator's browser โ it carries a bearer token, so it is encrypted, not merely signed.
- The API verifies every token against your realm's published keys independently, rather than trusting the dashboard's word for it.
- Workspace membership and roles come from a claim your realm emits.
1 ยท Realm and client
Pick or create a realm. Note its issuer URL โ
https://<keycloak-host>/realms/<realm>. Everything else is
discovered from it.
Create an OpenID Connect client with the standard flow enabled. Confidential clients (client authentication on, with a secret) and public clients (PKCE, no secret) both work.
Set the URLs. Valid redirect URI
https://<dashboard-host>/api/auth/oidc/callback; valid post-logout
redirect URI https://<dashboard-host>/sign-in; web origin
https://<dashboard-host>.
Scopes. openid profile email. Keep email โ it is what names
the operator in audit entries.
Add an audience mapper. Keycloak mints aud: account alongside
your client id, so add an Audience protocol mapper that includes
the client id. Sentisec's audience setting accepts a
comma-separated list and matches on any entry, so configure it to
accept both.
2 ยท The workspace claim
Workspace membership rides on one claim, sentisec_workspaces by
default. Add a protocol mapper that emits it into both the ID token and
the access token โ a User Attribute mapper with a JSON claim type is
the usual way to carry a per-user map.
Four shapes are accepted and all normalise to the same workspace-to-role mapping:
| Claim value | Reading |
|---|---|
{"<workspace-id>": "admin"} | object: id โ role |
[{"id": "<workspace-id>", "role": "admin"}] | array of objects |
["<workspace-id>", "<workspace-id>:admin"] | array of strings, optional :role |
"<workspace-id>:admin <workspace-id-2>" | one space- or comma-separated string |
Rules worth knowing before you write the mapper:
- A bare id with no role means member. Least privilege is the default everywhere; an omitted role is never read as admin.
- Role spellings match case-insensitively.
admin,owner, and theorg:admin/workspace:adminprefixed forms all mean admin. Anything else means member. - Dotted claim names work. Set the claim name to
realm_access.sentisec_workspacesif your mapper nests the value, which Keycloak realm mappers commonly do. - An unparseable claim yields no roles at all, which denies. It never falls back to an admit.
The admin claim
sentisec_admin is a global elevation switch. A truthy value โ true,
1, yes, on, or a list containing admin โ makes the subject an
admin of every workspace their workspace claim already names.
It never grants membership on its own. A global admin whose workspace claim is empty sees nothing. Elevation and membership are separate decisions, so a mis-mapped boolean cannot hand a stranger the whole install.
Both claim names are configurable if sentisec_workspaces and
sentisec_admin collide with something you already emit.
3 ยท Configure the install
Both halves โ the API and the dashboard โ need pointing at your realm: the issuer, the client id and secret, the audience to accept, the claim names above, and a session secret for the dashboard's cookie. The configuration reference ships with your install package; request access if you do not have it.
Three behaviours are worth knowing up front, because each has bitten someone:
- The mode is all-or-nothing. OIDC settings stay inert until the install is switched into OIDC mode, so a half-finished configuration cannot half-apply.
- The dashboard image must be built for the mode it runs. The browser half reads the auth mode from a value fixed at image build time, so setting it only at runtime is not enough. An image built for one mode and run in the other logs a loud incoherence warning at startup rather than failing quietly.
- Do not leave hosted-identity configuration in place. If the API still sees hosted-identity settings, it installs a verification override at startup that shadows the OIDC path, and sign-in fails with nothing obviously wrong in your OIDC configuration. Clear them, and check the startup logs first if logins fail โ the warning is there.
The issuer is derived automatically from a Keycloak-shaped keys URL. For any other layout, set it explicitly: if the issuer can be neither read nor derived, OIDC stays off rather than verifying against an unpinned issuer.
4 ยท First sign-in
There is no user directory to populate. Workspaces are created by your operator at install time, and a person becomes a member of the workspaces their token names on their first verified sign-in.
Each first-time pairing of workspace and subject writes one
workspace.member_added audit entry marked via: oidc_jit. Repeat
sign-ins are idempotent and write nothing further. The behaviour can be
switched off if you would rather provision membership yourself.
Provisioning runs after the verification decision and is best-effort: a failed write is logged, and never turns a valid sign-in into a rejection nor an invalid one into an admission.
5 ยท Verify
Discovery reaches the realm. From the dashboard host,
https://<keycloak-host>/realms/<realm>/.well-known/openid-configuration
must resolve and return metadata.
Sign in. The dashboard redirects to Keycloak, and Keycloak
returns you to /api/auth/oidc/callback and on into the app.
Check the workspace picker. It should list exactly the workspaces your claim names โ no more, no fewer.
Check the roles. An admin-only action succeeds for a subject mapped to admin and is refused for one mapped to member. The control plane is the enforcement point; the interface only predicts it.
Check the audit trail. The workspace audit log carries a
workspace.member_added entry for each first sign-in. See
Audit and metrics.
What this mode does not cover
Real constraints, not configuration mistakes:
- Workspace lifecycle is operator-managed. Creating and deleting workspaces is not exposed in the interface in this mode; workspaces come from your install-time configuration.
- Per-user interface preferences do not persist. Saved views, the time-range selection, and guided-tour state fall back to defaults every session. The dashboard warns once rather than failing.
- Hosted-plan surfaces are off. The billing portal, organisation invitations, and the SCIM path described for hosted tenants do not apply here.
- Authorization code flow only. No SAML and no IdP-initiated sign-in. Front-channel logout is wired, with a local-only fallback when your provider publishes no end-session endpoint; back-channel logout is not available.
If one of these blocks your deployment, tell us โ knowing which one matters changes what we build next.
Related
- Single Sign-On โ the hosted path
- Roles and permissions โ what the claim grants
- In-VPC deployment