Chapter 6 of 15
Setting Up OIDC SSO
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 useRS256by 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
groupsclaim 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 usingHS256, 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
groupsorak_groupsclaim (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 readsAPP_ENVto determine the runtime environment (e.g.production).NODE_ENVis accepted as a backward-compatibility alias butAPP_ENVis 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 oidcis 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
- Open PatchMon in your browser
- You should see a "Login with SSO" button (or your custom
OIDC_BUTTON_TEXT) - Click it and you'll be redirected to your IdP
- Authenticate with your IdP credentials
- 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:
Option A - Use the Setup Wizard (Recommended)
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:
- Set
OIDC_AUTO_CREATE_USERS=true - The setup wizard is automatically bypassed when OIDC with auto-create is enabled
- 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
- 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
pictureclaim is present - First name and last name: from
given_nameandfamily_nameclaims - 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_LOGGINGdefaults totruefrom 2.0.3, but an explicitfalsein.envor 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_LEVELis aboveinfo. The confirmation line is logged at info level, sowarnorerrorhides 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 configorpartially configuredin 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 configorpartially configured OIDC_ENABLEDis nottrueand 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 asauthentik Self-signed Certificate. An empty Signing Key is what causes this - The Redirect URI in your IdP does not match
OIDC_REDIRECT_URIexactly (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_URLis 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
groupsscope is included inOIDC_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 emitsgroups - Group matching is case-insensitive, so
patchmon adminsmatchesPatchMon 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). SetAPP_ENVtoproductionin your environment.NODE_ENVis 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