Skip to content

Single Sign On

Ed Mozley edited this page May 30, 2026 · 4 revisions

Single Sign-On (SSO / OIDC)

FreeITSM supports single sign-on via OpenID Connect (OIDC), letting analysts sign in through an external identity provider (IdP) instead of β€” or alongside β€” a local username and password.

It's a generic OIDC implementation: one code path works with any compliant provider, including Keycloak, Microsoft Entra ID (Azure AD), Okta, Auth0, Google Workspace and Authentik. You can configure several providers at once, which makes phased rollouts and side-by-side pilots straightforward (e.g. most users on Keycloak while a group trials Entra).

Worked example: see ↳ Setting up SSO with Keycloak (Docker) for a complete, copy-paste walkthrough using a local Keycloak instance.

Key ideas

  • Driven by discovery. Everything hangs off the provider's discovery document at <issuer>/.well-known/openid-configuration. You only supply a display name, issuer URL, client ID and client secret β€” the authorization, token, JWKS and logout endpoints are all read from discovery. That's why one form works for every IdP.
  • Local login stays. SSO sits next to local username/password login, which remains as a break-glass fallback. You can never be locked out by a misconfigured or offline IdP.
  • The analysts table is the identity store. An SSO login is mapped to an analyst row; permissions, ticket assignment, teams etc. all continue to work exactly as for local users.

How a sign-in works

  1. The login page offers an email-first box (type your email β†’ routed to your provider) plus a "Sign in with …" button per enabled provider.
  2. FreeITSM redirects the browser to the provider using the Authorization Code flow with PKCE (S256), carrying a state (CSRF protection) and nonce (replay protection).
  3. The user authenticates at the IdP (which also handles its own MFA).
  4. The provider redirects back to …/api/auth/oidc_callback.php. FreeITSM exchanges the code for tokens over a server-to-server back channel, then validates the ID token: signature against the provider's JWKS, plus issuer, audience, nonce and expiry.
  5. The identity is mapped to an analyst (see below), the session is established, and the user lands in the app.

JWT/JWKS validation uses the vendored firebase/php-jwt library (includes/vendor/firebase-jwt/) β€” the security-critical token checking is never hand-rolled.

Configuring a provider

Go to System β†’ Single Sign-On.

Global settings

  • Enable single sign-on β€” the master switch. Turning it off instantly reverts everyone to local login (break-glass kill switch).
  • Allow local login β€” keep the local username/password form available. Leave on unless you want an SSO-only experience.

Redirect URI

The page shows the exact redirect URI to register in your IdP (e.g. http://localhost/freeitsm-app/api/auth/oidc_callback.php). Copy it into the provider's valid redirect URIs list β€” it's where the IdP sends users back after they authenticate.

Adding a provider

Click Add and fill in:

Field What it is
Display name The label on the login button, e.g. Sign in with Keycloak
Issuer URL The provider's base URL (see Issuer URLs below). The Test button validates it by fetching the discovery document.
Client ID The client/application identifier created in the provider
Client secret The client's secret β€” stored encrypted (AES-256-GCM) and never returned to the browser
Scopes OIDC scopes; default openid email profile
Enabled Show this provider's button on the login page
Auto-create users (JIT) Create an analyst automatically on first login (see below)
Default module access Modules granted to JIT-created users β€” blank means full access, so set this for pilots

Account mapping & provisioning

On a successful sign-in, FreeITSM resolves the analyst in this order:

  1. Existing identity link β€” a stored link between the provider and the IdP's sub (subject) claim.
  2. Email match β€” an active analyst whose email matches the token's (the email must be verified at the IdP). The identity is then linked for next time.
  3. Just-in-time (JIT) provisioning β€” if the provider has Auto-create users enabled, a new analyst is created (with an unusable local password), assigned to that provider, and granted the provider's default modules.

If none apply (no match and JIT is off), the sign-in is rejected with a readable message.

Strict isolation

An analyst may only sign in via the provider they're assigned to (analysts.auth_provider_id). An SSO login can never silently take over a local account or an account assigned to a different provider. This keeps parallel pilots cleanly separated.

Assigning existing users to a provider

JIT only assigns brand-new users. To move existing analysts onto SSO, open Tickets β†’ Settings β†’ Analysts, edit the analyst, and set Sign-in method to the provider. On their next SSO sign-in their identity is linked automatically (matched by email).

Break-glass & SSO-only mode

  • When SSO is active the login page leads with email-first + provider buttons; the local form is tucked behind a "Sign in with a local account" link.
  • Setting Allow local login to off hides that link too (a clean SSO-only look), but local auth is never hard-disabled: the URL …/login.php?local=1 and the master kill switch always restore local login, so no one can be locked out.
  • MFA: SSO users authenticate (and do MFA) at the identity provider, so FreeITSM skips its own local TOTP step for them.

Single logout

Signing out of FreeITSM also ends the session at the identity provider via its end_session_endpoint, so the user isn't silently re-authenticated on the next visit.

Security notes

  • Authorization Code + PKCE (S256), with state and nonce validation.
  • ID-token signature verified against the provider's JWKS; issuer, audience, nonce and expiry all checked.
  • Client secrets encrypted at rest and never sent to the browser (the config UI shows only whether a secret is stored).
  • The email-resolver endpoint returns "local" for unknown emails, so it doesn't reveal whether an account exists.

Issuer URLs by provider

Provider Issuer URL pattern
Keycloak https://<host>/realms/<realm>
Microsoft Entra ID https://login.microsoftonline.com/<tenant-id>/v2.0
Okta https://<org>.okta.com (or an /oauth2/<server> auth server)
Auth0 https://<tenant>.<region>.auth0.com/
Google Workspace https://accounts.google.com
Authentik https://<host>/application/o/<app-slug>/

Provider-specific concepts (Entra tenant + app registration, Keycloak realm, Okta org) all collapse into the issuer URL plus the client ID/secret β€” there's nothing else provider-specific to enter.

Database

  • auth_providers β€” one row per configured IdP (display name, issuer, client ID, encrypted secret, scopes, enabled, JIT toggle, default modules).
  • analyst_sso_identities β€” links an analyst to their identity at a provider (the sub claim), unique per (provider, subject).
  • analysts.auth_provider_id β€” which provider an analyst is assigned to (NULL = local password user).

Related

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally