Single sign-on (SSO)
Single sign-on lets people sign in to the admin with their company account instead of a NodePress password. NodePress works with any identity provider that supports OpenID Connect (OIDC): Google Workspace, Microsoft Entra ID, Okta, Auth0, Keycloak and others.
When SSO is set up, the login page shows a Continue with name button next to the password form. Password sign-in keeps working.
SSO is configured with environment variables on the backend. There’s no admin screen for it.
Setting it up
Section titled “Setting it up”-
Register NodePress with your provider as a web application using the authorization code flow. Set its redirect (callback) URL to:
https://<your admin site>/api/auth/oidc/callbackThat’s
SITE_URLfollowed by/api/auth/oidc/callback, for examplehttps://cms.example.com/api/auth/oidc/callback. It goes through the admin’s own/apiproxy, so the sign-in cookie comes back on the same host even when the backend runs elsewhere. -
Copy the client ID and client secret the provider gives you.
-
Set the variables in
backend/.env:backend/.env OIDC_ISSUER=https://accounts.google.comOIDC_CLIENT_ID=1234567890-abc.apps.googleusercontent.comOIDC_CLIENT_SECRET=…OIDC_PROVIDER_NAME=GoogleOIDC_ALLOWED_DOMAINS=example.com -
Restart the backend. The login page now shows Continue with Google.
SSO is on as soon as OIDC_ISSUER, OIDC_CLIENT_ID and OIDC_CLIENT_SECRET are all set. Setting the issuer without the other two stops the server at startup with an error.
Variables
Section titled “Variables”| Variable | Default | Meaning |
|---|---|---|
OIDC_ISSUER |
The provider’s issuer URL. NodePress reads <issuer>/.well-known/openid-configuration. |
|
OIDC_CLIENT_ID |
Client ID from the provider | |
OIDC_CLIENT_SECRET |
Client secret from the provider | |
OIDC_PROVIDER_NAME |
SSO |
Button label: “Continue with name” |
OIDC_SCOPES |
openid email profile |
Scopes to request. Must include openid and give NodePress an email claim. |
OIDC_REDIRECT_URI |
<SITE_URL>/api/auth/oidc/callback |
Override the callback URL. It must match the provider’s setting exactly. |
OIDC_ALLOWED_DOMAINS |
any | Comma-separated email domains allowed to sign in, such as example.com,example.org |
OIDC_AUTO_CREATE |
false |
true creates an account on first sign-in; false only lets in people who already have one |
OIDC_DEFAULT_ROLE |
viewer |
Role for accounts created on first sign-in: admin, editor, contributor or viewer |
OIDC_ROLE_CLAIM |
ID token claim that holds the user’s groups or roles, such as groups or roles |
|
OIDC_ROLE_MAP |
Group-to-role mapping, such as cms-admins:admin,cms-editors:editor |
Common issuer URLs:
| Provider | OIDC_ISSUER |
|---|---|
| Google Workspace | https://accounts.google.com |
| Microsoft Entra ID | https://login.microsoftonline.com/<tenant-id>/v2.0 |
| Okta | https://<your-org>.okta.com |
| Auth0 | https://<your-tenant>.auth0.com |
| Keycloak | https://<host>/realms/<realm> |
Who can sign in
Section titled “Who can sign in”A person is let in when all of these hold:
- The provider returns an
emailclaim, and doesn’t mark it unverified (email_verified: false). - When
OIDC_ALLOWED_DOMAINSis set, the email’s domain is in the list. - A NodePress user with that email exists (matched case-insensitively), or
OIDC_AUTO_CREATE=true.
With auto-create off, sign-in stays invite-only: invite people first, then they can use either the button or the password they set. Accounts created by SSO get a random password nobody knows, so they sign in through the provider only.
If sign-in fails, the user lands back on the login page with the reason, such as “Your email domain is not allowed to sign in here” or “No account exists for this email — ask an admin to invite you”.
Mapping groups to roles
Section titled “Mapping groups to roles”To manage roles at the provider, set OIDC_ROLE_CLAIM to the claim that lists the user’s groups and OIDC_ROLE_MAP to pairs of group:role:
OIDC_ROLE_CLAIM=groupsOIDC_ROLE_MAP=cms-admins:admin,cms-editors:editor,cms-writers:contributor,cms-reviewers:reviewer- The claim can be a single value or a list.
- If a user is in several mapped groups, the highest role wins (
admin>editor>contributor>viewer). A custom role, likereviewerabove, ranks as the role it’s based on. - The mapped role is applied on every sign-in, so the provider is the source of truth: a role an admin changes in NodePress is overwritten at the user’s next SSO sign-in.
- A user in none of the mapped groups keeps their current role. A new user in none of them gets
OIDC_DEFAULT_ROLE.
Make sure your provider actually puts the claim in the ID token; many only do so after you configure it (for example a “groups claim” setting).
How the flow works
Section titled “How the flow works”- Continue with… opens
GET /api/auth/oidc/login. NodePress creates a random state, nonce and PKCE verifier, stores them in a short-lived signed cookie (np_oidc, 10 minutes), and redirects to the provider. - The provider sends the browser back to
/api/auth/oidc/callbackwith a code. NodePress checks the state, exchanges the code (with the PKCE verifier and the client secret), and verifies the ID token: signature against the provider’s published keys, issuer, audience, expiry and nonce. - NodePress finds or creates the user, then redirects to the admin’s
/sso-callbackpage with a one-time code in the URL fragment, which browsers never send to servers. - The admin swaps that code for a normal session with
POST /api/auth/oidc/exchange. The code is valid for 60 seconds and works once.
From there, the session is the same as a password sign-in: an access token and the rotating refresh cookie (Authentication). SSO sign-ins are recorded in the audit log.
| Endpoint | What it does |
|---|---|
GET /api/auth/oidc/config |
{ "enabled": true, "providerName": "Google" }, used by the login page |
GET /api/auth/oidc/login?returnTo=/entries |
Starts sign-in. returnTo must be a path on the admin site. |
GET /api/auth/oidc/callback |
The provider’s redirect target |
POST /api/auth/oidc/exchange |
{ code } → access token and refresh cookie |
Things to know
Section titled “Things to know”- Signing algorithms. ID tokens must be signed with an asymmetric algorithm (RS256 and the other RS, PS and ES variants). Tokens signed with a shared secret (HS256) are refused.
- Two-factor. SSO users aren’t asked for a NodePress 2FA code. Require it at the provider.
- Several backend instances. The one-time code from step 3 is kept in the cache. Set
REDIS_URLso the instance that handles the exchange can find it; see Scaling. - Signing out of NodePress doesn’t sign the user out of the provider.