Skip to main content
PathMon

Chapter 6 of 15

Setting Up OIDC SSO

Updated Read the full guide

Overview

PatchMon supports OpenID Connect (OIDC) authentication, allowing users to log in via an external Identity Provider (IdP) instead of, or in addition to, local username/password credentials.

Supported Providers

Any OIDC-compliant provider works, including:

  • Authentik
  • Keycloak
  • Okta
  • Azure AD (Entra ID)
  • Google Workspace

What You Get

  • SSO login via a configurable button on the login page
  • Automatic user provisioning on first login (no need to create accounts manually)
  • Group-based role mapping so your IdP controls who is an admin, user, or readonly viewer
  • Optional: disable local password login entirely and enforce SSO for all users

Prerequisites

  • PatchMon already installed and running
  • An OIDC-compatible Identity Provider with an OAuth2/OIDC application configured
  • HTTPS in production (OIDC routes enforce HTTPS when OIDC_ENFORCE_HTTPS=true, which is the default)

Step 1 - Create an OIDC Application in Your IdP

Create a new OAuth2 / OIDC application in your Identity Provider with the following settings:

Setting Value
Application type Web application / Confidential client
Redirect URI https://patchmon.example.com/api/v1/auth/oidc/callback
Scopes openid, email, profile, groups
Grant type Authorization Code
Token endpoint auth Client Secret (Basic)
ID token signing An asymmetric algorithm: RS256 (or RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512, EdDSA)

After creating the application, note the Client ID and Client Secret as you'll need both.

Important: PatchMon cannot accept HS256-signed ID tokens. PatchMon validates ID tokens against the public keys published at your IdP's JWKS endpoint, so the token must be signed asymmetrically with a certificate or key pair. Symmetric HMAC signing (HS256, HS384, HS512), where the client secret doubles as the signing key, is rejected. Most identity providers use RS256 by default, but Authentik does not unless you tell it to. See the Authentik note below.

Tip: If you plan to use group-based role mapping, ensure your IdP includes the groups claim in the ID token. In Authentik, this is enabled by default. In Keycloak, you may need to add a "Group Membership" mapper to the client scope.

Provider-Specific Notes

Authentik:

  • Create an OAuth2/OIDC Provider, then create an Application linked to it
  • Set a Signing Key on the provider. This is required and is the single most common cause of a failed Authentik setup. Open the provider, expand Advanced protocol settings, and set Signing Key to a certificate, for example the built-in authentik Self-signed Certificate. If Signing Key is left empty, Authentik signs ID tokens symmetrically with the client secret using HS256, which PatchMon rejects. Every login then fails with a generic "Authentication failed" message on the login page
  • Issuer URL format: https://auth.example.com/application/o/patchmon/
  • Groups are included via the groups or ak_groups claim (both are supported)

Keycloak:

  • Create a Client with Access Type confidential
  • Issuer URL format: https://keycloak.example.com/realms/your-realm
  • Add a "Group Membership" protocol mapper to include groups in the token

Okta / Azure AD:

  • Create an OIDC Web Application
  • Ensure groups are included in the ID token claims

Step 2 - Configure PatchMon

Add the following environment variables to your .env file (for Docker deployments) or your server environment.

Required Variables

OIDC_ENABLED=true
OIDC_ISSUER_URL=https://auth.example.com/application/o/patchmon/
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
OIDC_REDIRECT_URI=https://patchmon.example.com/api/v1/auth/oidc/callback
Variable Description
OIDC_ENABLED Set to true to enable OIDC
OIDC_ISSUER_URL Your IdP's issuer / discovery URL
OIDC_CLIENT_ID Client ID from your IdP application
OIDC_CLIENT_SECRET Client secret from your IdP application
OIDC_REDIRECT_URI Must match exactly what you configured in your IdP

Optional Variables

OIDC_SCOPES=openid email profile groups
OIDC_AUTO_CREATE_USERS=false
OIDC_DEFAULT_ROLE=user
OIDC_DISABLE_LOCAL_AUTH=false
OIDC_BUTTON_TEXT=Login with SSO
OIDC_SESSION_TTL=600
OIDC_POST_LOGOUT_URI=https://patchmon.example.com/login
OIDC_ENFORCE_HTTPS=true
OIDC_SYNC_ROLES=false
Variable Default Description
OIDC_SCOPES openid email profile groups Space-separated scopes to request. Include groups for role mapping
OIDC_AUTO_CREATE_USERS false When true, automatically creates a PatchMon account on first OIDC login. When false, the user must already exist in PatchMon (matched by email)
OIDC_DEFAULT_ROLE user Role assigned when a user doesn't match any group mapping
OIDC_DISABLE_LOCAL_AUTH false When true, hides the username/password fields and only shows the SSO button
OIDC_BUTTON_TEXT Login with SSO Label shown on the SSO login button
OIDC_SESSION_TTL 600 Seconds the OIDC login state is valid. If the user takes longer than this at the IdP, the session expires and they must try again
OIDC_POST_LOGOUT_URI <CORS_ORIGIN>/login Where to redirect after a logout. Defaults to the PatchMon login page
OIDC_ENFORCE_HTTPS true When true, enforces HTTPS on OIDC login and callback routes. Set to false only for local development
OIDC_SYNC_ROLES false When true, the user's role is updated on every login based on current group membership. When false, roles are managed locally in PatchMon and OIDC login does not change them

Note on APP_ENV: PatchMon reads APP_ENV to determine the runtime environment (e.g. production). NODE_ENV is accepted as a backward-compatibility alias but APP_ENV is preferred.


Step 3 - Group-Based Role Mapping (Optional)

Map your IdP groups to PatchMon roles so that role assignments stay in sync with your directory. Group matching is case-insensitive.

Role Hierarchy

PatchMon checks group membership in this order (highest priority first):

PatchMon Role Required IdP Group(s) Description
Super Admin Member of OIDC_SUPERADMIN_GROUP Full access including managing other superadmins
Admin Member of OIDC_ADMIN_GROUP Full access
Host Manager Member of OIDC_HOST_MANAGER_GROUP Manage hosts and groups
User Member of OIDC_USER_GROUP Standard access with data export
Readonly Member of OIDC_READONLY_GROUP View-only access
Default None of the above Gets OIDC_DEFAULT_ROLE (defaults to user)

Priority: If a user is in multiple groups, the highest-priority role wins. The priority order from highest to lowest is: Super Admin > Admin > Host Manager > Readonly > User.

Environment Variables

OIDC_ADMIN_GROUP=PatchMon Admins
OIDC_USER_GROUP=PatchMon Users
OIDC_SUPERADMIN_GROUP=PatchMon SuperAdmins
OIDC_HOST_MANAGER_GROUP=PatchMon Host Managers
OIDC_READONLY_GROUP=PatchMon Readonly
OIDC_SYNC_ROLES=true
Variable Description
OIDC_ADMIN_GROUP IdP group name that maps to Admin role
OIDC_USER_GROUP IdP group name that maps to User role
OIDC_SUPERADMIN_GROUP IdP group name that maps to Super Admin
OIDC_HOST_MANAGER_GROUP IdP group name that maps to Host Manager role
OIDC_READONLY_GROUP IdP group name that maps to Readonly role
OIDC_SYNC_ROLES When true, the user's role is updated on every login based on current group membership. When false (default), roles are managed locally in PatchMon and OIDC login does not change them

You only need to define the groups you intend to use. Any variables left unset are simply ignored.


Step 4 - Restart PatchMon

After updating your .env file, restart the server so it picks up your OIDC configuration:

# Docker
docker compose restart patchmon-server

# Or if rebuilding
docker compose up -d --force-recreate patchmon-server

# Native systemd installation
sudo systemctl restart <your-domain>

This check needs ENABLE_LOGGING=true, which is the default from 2.0.3. If you have set ENABLE_LOGGING=false, or you are on 2.0.2 or earlier where false was the default, the server writes no logs at all and the commands below return nothing regardless of whether OIDC loaded. Keep LOG_LEVEL at info or lower too, since the confirmation line is logged at info level.

# Docker
docker compose logs patchmon-server | grep -i oidc

# Native systemd
journalctl -u <your-domain> | grep -i oidc

You should see a line confirming SSO is enabled, containing the message OIDC SSO enabled; provider discovery is deferred to the first login attempt along with the issuer and client ID that were loaded.

PatchMon does not contact your identity provider at startup. Provider discovery (the request to .well-known/openid-configuration) is deliberately deferred until the first login attempt, so a temporarily unreachable IdP cannot stop the server from booting. This line therefore confirms only that your four required variables were read and that the SSO button will appear. It does not prove your IdP is reachable or that your provider is configured correctly. The first login attempt is what tests that, and any failure is logged at that point.

The surrounding format depends on APP_ENV. In production, which is the default, logs are JSON:

{"time":"2026-08-12T20:34:34.526Z","level":"INFO","msg":"OIDC SSO enabled; provider discovery is deferred to the first login attempt","version":"2.0.3","port":3000,"issuer":"https://auth.example.com/application/o/patchmon/","client_id":"patchmon","source":"environment variables"}

With APP_ENV set to anything else, the same record is printed as plain text:

time=2026-08-12T20:34:34.526Z level=INFO msg="OIDC SSO enabled; provider discovery is deferred to the first login attempt" version=2.0.3 port=3000 issuer=https://auth.example.com/application/o/patchmon/ client_id=patchmon source="environment variables"

The source field tells you which configuration won: environment variables or database settings. If you edited SSO settings in the web UI but see environment variables, your .env is overriding them.

If you see OIDC is enabled but missing required config: ..., one or more of the four required variables is empty. If you see OIDC is partially configured via env vars but SSO is disabled, you have set some but not all of them.

Note: Releases before 2.0.3 logged nothing at all on a successful OIDC configuration. If you are on 2.0.2 or older, an empty grep -i oidc is expected and does not mean OIDC failed to load. Check whether the SSO button appears on the login page instead.


Step 5 - Test the Login

  1. Open PatchMon in your browser
  2. You should see a "Login with SSO" button (or your custom OIDC_BUTTON_TEXT)
  3. Click it and you'll be redirected to your IdP
  4. Authenticate with your IdP credentials
  5. You'll be redirected back to PatchMon and logged in

If OIDC_AUTO_CREATE_USERS is true, a PatchMon account is created automatically using your email address. The username is derived from the email prefix (e.g. john.doe@example.com becomes john.doe).


First-Time Setup (No Users Exist Yet)

When PatchMon has no users in the database, it displays a setup wizard. You have two options:

Complete the setup wizard to create your first admin account. This account is created as a Super Admin with full access. You can then enable OIDC from Settings afterwards.

Option B - Log In via OIDC Directly

If you've pre-configured OIDC via environment variables before first boot:

  1. Set OIDC_AUTO_CREATE_USERS=true
  2. The setup wizard is automatically bypassed when OIDC with auto-create is enabled
  3. The first user to log in via OIDC is automatically promoted to Super Admin, regardless of group mapping, to ensure the system always has an admin
  4. Subsequent OIDC users get roles based on group mapping or the default role as normal

Note: You do not need to configure group mapping for the first user. The auto-promotion happens because PatchMon detects no admin exists yet.


What Syncs from Your IdP

On every OIDC login, PatchMon automatically syncs the following from your Identity Provider:

  • Avatar / profile picture: synced if the picture claim is present
  • First name and last name: from given_name and family_name claims
  • Email: used for matching and account linking

The following is only synced when OIDC_SYNC_ROLES=true:

  • Role: based on group membership. When sync is off, roles are managed locally in PatchMon and OIDC login does not change them. You can use OIDC for authentication while still managing roles manually.

Account Linking

If a local PatchMon user already exists with the same email as the OIDC user, PatchMon will automatically link the accounts, but only if the email is marked as verified by the IdP. This prevents account takeover via unverified emails.


Disabling Local Authentication

To enforce SSO for all users, set:

OIDC_DISABLE_LOCAL_AUTH=true

This hides the username/password fields on the login page and only shows the SSO button. Local authentication is only actually disabled if OIDC is also enabled and successfully initialised. This safety check prevents you from being locked out if OIDC is misconfigured.

Important: Ensure at least one OIDC user has admin access before enabling this, or you may lose the ability to manage PatchMon.


Complete Example Configuration

Authentik

# .env
APP_ENV=production
OIDC_ENABLED=true
OIDC_ISSUER_URL=https://authentik.example.com/application/o/patchmon/
OIDC_CLIENT_ID=patchmon
OIDC_CLIENT_SECRET=your-client-secret-here
OIDC_REDIRECT_URI=https://patchmon.example.com/api/v1/auth/oidc/callback
OIDC_SCOPES=openid email profile groups
OIDC_AUTO_CREATE_USERS=true
OIDC_DEFAULT_ROLE=user
OIDC_BUTTON_TEXT=Login with Authentik
OIDC_ADMIN_GROUP=PatchMon Admins
OIDC_USER_GROUP=PatchMon Users
OIDC_SYNC_ROLES=true

Reminder: On the Authentik side, the provider's Signing Key (under Advanced protocol settings) must be set to a certificate. Leaving it empty makes Authentik sign ID tokens with HS256, which PatchMon rejects, and every login will fail with "Authentication failed".

Keycloak

# .env
APP_ENV=production
OIDC_ENABLED=true
OIDC_ISSUER_URL=https://keycloak.example.com/realms/your-realm
OIDC_CLIENT_ID=patchmon
OIDC_CLIENT_SECRET=your-client-secret-here
OIDC_REDIRECT_URI=https://patchmon.example.com/api/v1/auth/oidc/callback
OIDC_SCOPES=openid email profile groups
OIDC_AUTO_CREATE_USERS=true
OIDC_DEFAULT_ROLE=user
OIDC_BUTTON_TEXT=Login with Keycloak
OIDC_ADMIN_GROUP=PatchMon Admins
OIDC_USER_GROUP=PatchMon Users
OIDC_SYNC_ROLES=true

Troubleshooting

OIDC Not Initialising

Logs show: OIDC is enabled but missing required config: ...

All four required variables must be set: OIDC_ISSUER_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI. Check for typos or empty values. A related message, OIDC is partially configured via env vars but SSO is disabled, means some but not all of the four are set.

No OIDC Lines in the Startup Logs

On a healthy configuration, PatchMon 2.0.3 and later logs OIDC SSO enabled; provider discovery is deferred to the first login attempt at startup. It does not contact your identity provider at startup, so there are never any discovery or connection messages to look for.

An empty grep -i oidc has four possible causes, and only the last one is a problem with your SSO configuration:

  • Logging is off. ENABLE_LOGGING defaults to true from 2.0.3, but an explicit false in .env or Settings > Environment is still honoured, and it was the default on earlier releases. With logging disabled the server writes nothing at all, so this check tells you nothing
  • LOG_LEVEL is above info. The confirmation line is logged at info level, so warn or error hides it
  • You are on 2.0.2 or older. A correct configuration logged nothing at all on those releases
  • Your configuration did not resolve. Check for missing required config or partially configured in the same output

If logging is off and you would rather not turn it on, check whether the SSO button appears on the login page instead. That is driven by the same resolved configuration.

You may also see a second OIDC line warning that role sync cannot grant superadmin. That is unrelated to whether SSO loaded and is covered under Step 3.

SSO Button Not Appearing

The button appears when all four required variables resolve to non-empty values (from environment variables, or from Settings in the web UI). Because PatchMon does not contact your IdP until someone actually logs in, an unreachable or misconfigured IdP does not hide the button. If the button is missing, the problem is in your configuration values rather than your IdP:

  • One of the four required variables is empty or misspelled. Check the startup logs for missing required config or partially configured
  • OIDC_ENABLED is not true and no OIDC settings have been saved in the web UI
  • The client secret was saved in the web UI but cannot be decrypted, which is logged as OIDC client secret could not be decrypted; treating OIDC as unconfigured. This usually means your encryption key changed. Re-enter and save the secret

"Authentication Failed" After Redirect

This is the generic error for any failure during the token exchange, after your IdP has sent the user back to PatchMon. Check the server logs for the line beginning oidc exchange failed, which contains the specific reason. Common causes:

  • Your IdP is signing ID tokens with HS256. The log contains unexpected signature algorithm "HS256". PatchMon only accepts asymmetrically signed tokens. In Authentik, open the OAuth2/OIDC provider, expand Advanced protocol settings, and set Signing Key to a certificate such as authentik Self-signed Certificate. An empty Signing Key is what causes this
  • The Redirect URI in your IdP does not match OIDC_REDIRECT_URI exactly (including trailing slashes)
  • Cookies are being blocked (OIDC uses httpOnly cookies for session state)
  • Your IdP does not support PKCE (PatchMon uses the S256 code challenge)

"Failed to reach the OIDC provider" When Clicking the SSO Button

You get this immediately on clicking the SSO button, before your IdP's login page ever appears. The browser shows it as a bare JSON response rather than a styled error page, and the logs show oidc auth url failed.

PatchMon fetches your IdP's discovery document (.well-known/openid-configuration) on the first login attempt rather than at startup, so problems reaching or validating that document surface at this point:

  • PatchMon cannot reach the IdP from inside the container (DNS, firewall, or network policy)
  • OIDC_ISSUER_URL is wrong. It must not include .well-known/openid-configuration, which PatchMon appends itself
  • The issuer URL in the discovery document does not match OIDC_ISSUER_URL. For Authentik, the trailing slash matters
  • The IdP's TLS certificate is not trusted by the PatchMon container

"Session Expired" Error

The OIDC login state has a configurable window (default 600 seconds via OIDC_SESSION_TTL). If the user takes longer than this at the IdP, the session expires. Simply try logging in again, or increase OIDC_SESSION_TTL if this is happening frequently.

User Gets Wrong Role

  • Check that the groups scope is included in OIDC_SCOPES
  • Verify your IdP is including groups in the ID token (not just the access token)
  • If the logs show oidc no groups in token, your IdP sent no groups at all. Configure it to include the groups claim. In Authentik this means adding a Scope Mapping that emits groups
  • Group matching is case-insensitive, so patchmon admins matches PatchMon Admins

OIDC Banners / Restrictions Appearing When They Shouldn't

If you see "OIDC Authentication Enabled" banners on the Users or Roles settings pages, or the "Add User" / "Add Role" buttons are missing, OIDC_SYNC_ROLES is enabled. These restrictions only apply when role sync is active. If you want to use OIDC for login but manage roles locally, set OIDC_SYNC_ROLES=false (or leave it unset; it defaults to false).

"User Not Found" Error

OIDC_AUTO_CREATE_USERS is false (the default) and no matching PatchMon account exists. Either set OIDC_AUTO_CREATE_USERS=true or create the user account manually in PatchMon first (the email must match).

Debug Logging

For detailed OIDC troubleshooting, enable debug logging:

LOG_LEVEL=debug

Then check the server logs:

# Docker
docker compose logs -f patchmon-server | grep -i oidc

# Native systemd
journalctl -u <your-domain> -f | grep -i oidc

Security Notes

  • HTTPS is enforced for OIDC login and callback routes when OIDC_ENFORCE_HTTPS=true (the default). Set APP_ENV to production in your environment. NODE_ENV is also accepted as a backward-compatibility alias
  • PKCE (S256) is used for all authorization code exchanges
  • Tokens are stored in httpOnly cookies, not localStorage, to prevent XSS attacks
  • Client secrets should never be committed to version control
  • Account linking only occurs when the IdP reports the email as verified
  • Role sync can be disabled (OIDC_SYNC_ROLES=false, which is the default) if you prefer to manage roles manually in PatchMon after first login