08 / Administration
SSO Access Restrictions
On this page 11
A successful sign-in at your identity provider proves who someone is. It does not say whether that person may use this Synaplan instance. These settings decide that: only people from one organization, with a role, or with a set of permissions get in — and, if you want, only people who already have an account.
Available from Synaplan 5.4.0. Authoritative reference: the "OIDC access restrictions" block in backend/.env.example in the main repository.
What the rules check#
Every rule is optional. With none set, every authenticated identity is admitted — the behavior before these settings existed.
| Rule | Variable | A person gets in when… |
|---|---|---|
| Organization | OIDC_ORG_CODE |
the token names this organization in OIDC_ORG_CLAIM |
| Role | OIDC_REQUIRED_ROLE |
the token carries at least one of these roles (comma-separated, case-insensitive) |
| Permissions | OIDC_REQUIRED_PERMISSIONS |
the token carries all of these permissions (comma-separated, exact) |
| Account creation | OIDC_ALLOW_USER_PROVISIONING |
true (default): a first sign-in creates the account. false: only people who have signed in through SSO before |
All rules that are set must hold. Account creation is independent of REGISTRATION_ENABLED, which only governs email-and-password sign-up.
Where the values come from in the token:
| Variable | Default | Notes |
|---|---|---|
OIDC_ORG_CLAIM |
org_code |
Dot-notation path. A string, a list of codes, or a map keyed by code all work — Keycloak's organization claim is such a map |
OIDC_ROLE_CLAIMS |
realm_access.roles,resource_access.{client_id}.roles,groups |
The same paths that decide who becomes administrator. Plain strings and objects with a key (Kinde) are read |
OIDC_PERMISSIONS_CLAIM |
permissions |
Dot-notation path to a list of strings |
OIDC_ORG_CODE is also sent as org_code in the authorization request, which makes Kinde sign the person in to that organization. Providers that do not know the parameter ignore it; the token claim is checked either way.
Where the rules apply#
The rules run on the access token after its signature, issuer, expiry and audience were verified — never on userinfo, which cannot grant access. They are applied:
- at sign-in, before any account is created or updated,
- on every OIDC token refresh, so a role or organization removed at the provider ends the session at the next refresh,
- on requests that use the OIDC session cookie,
- on every OIDC bearer token (token exchange, MCP).
With any rule set, opaque (non-JWT) access tokens are refused: their claims cannot be checked locally. Use a provider configuration that issues JWT access tokens.
Two limits worth knowing:
- Keep
offline_accessinOIDC_SCOPESso the provider issues a refresh token. Without one the session runs on Synaplan's own app token until it expires, and a revocation reaches it only then. - Mobile app sign-ins are checked at sign-in and then run on the app token, so a revocation reaches them when that token expires.
What people see#
Someone who does not meet the rules sees one sentence on the sign-in page — their company account is not allowed to use this workspace, nothing was signed in, no account was created, and they should ask an administrator. The page never says which rule failed, so the policy cannot be probed.
The reason is in the backend log, without the token:
OIDC access denied by instance policy {"reason":"role_missing","context":"login","sub":"kp_…"}
reason |
Meaning |
|---|---|
org_claim_missing |
The organization claim is absent |
org_mismatch |
A different organization |
role_missing |
None of the required roles |
permission_missing |
At least one required permission is missing |
opaque_token |
Opaque access token while rules are set |
provisioning_disabled |
No account yet and account creation is off |
context is login, session (cookie or refresh), bearer, or provisioning.
Setting it up#
Kinde#
Kinde puts org_code, roles and permissions into the access token. Two Kinde-side steps:
- Register an API for the application and use its audience as
OIDC_BEARER_AUDIENCE. Synaplan requests that audience at sign-in; without it Kinde issues a token with an empty audience and the sign-in fails the audience check. - In the application's token settings, include roles (and permissions, if you use them) in the access token.
OIDC_DISCOVERY_URL=https://acme.kinde.com
OIDC_CLIENT_ID=…
OIDC_CLIENT_SECRET=…
OIDC_BEARER_AUDIENCE=https://synaplan.acme.example/api
# Only administrators of one organization, no self-registration
OIDC_ORG_CODE=org_yyyzzzzxxxxx
OIDC_ROLE_CLAIMS=roles
OIDC_REQUIRED_ROLE=admin
OIDC_ALLOW_USER_PROVISIONING=false
OIDC_ROLE_CLAIMS=roles also drives administrator promotion through OIDC_ADMIN_ROLES — see First-Run Setup & Administrators.
Keycloak#
# Members of the "acme" organization with the realm role synaplan-user
OIDC_ORG_CODE=acme
OIDC_ORG_CLAIM=organization
OIDC_REQUIRED_ROLE=synaplan-user
Add the organization client scope so the claim reaches the access token.
In the admin UI#
Operate → System configuration → Authentication (/admin/config?tab=auth) has an OIDC access restrictions section with the same fields, and OIDC_BEARER_AUDIENCE sits in the OIDC (Enterprise SSO) section. Values saved there are written to the backend environment and need a restart. A variable set in the container environment wins over the file.
Kubernetes (Helm)#
The synaplan chart maps the rules to oidc.accessPolicy:
oidc:
enabled: true
issuerURI: https://acme.kinde.com
clientId: synaplan
clientSecretRef: synaplan-oidc-credentials
bearerAudience: https://synaplan.acme.example/api
roleClaims: roles
accessPolicy:
orgCode: org_yyyzzzzxxxxx
requiredRole: admin
requiredPermissions: ""
allowUserProvisioning: false
The render fails when oidc.accessPolicy is set and the image is older than 5.4.0 — an older image would ignore the rules and admit every authenticated identity.
Onboarding with account creation off#
OIDC_ALLOW_USER_PROVISIONING=false admits only accounts that an earlier SSO sign-in created. An account created any other way — People in the admin UI, API provisioning, email and password — cannot be taken over by an SSO sign-in with the same email address; Synaplan never merges accounts across sign-in methods. To let a new person in, either keep account creation on and rely on the organization, role or permission rules (the usual setup), or turn it on while that person signs in for the first time.
Locked out?#
Access rules never block local recovery: php bin/console app:admin:reset-password --promote still creates or promotes a local administrator.
Related pages#
- First-Run Setup & Administrators — SSO-only instances and administrators from claims
- Kubernetes (Helm Charts) — chart installation
- People & groups — directory groups from the OIDC
groupsclaim