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_access in OIDC_SCOPES so 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:

text
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:

  1. 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.
  2. In the application's token settings, include roles (and permissions, if you use them) in the access token.
dotenv
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#

dotenv
# 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:

yaml
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.