Single sign-on & provisioning
NovuHub speaks two standards, so any identity provider works — Microsoft Entra ID, Okta, Google Workspace, Keycloak, Authentik, JumpCloud, OneLogin — without vendor-specific code.
- OpenID Connect (OIDC) for sign-in: authorization-code flow with PKCE, discovered from the issuer's
/.well-known/openid-configuration. - SCIM 2.0 for provisioning: your IdP creates, updates and deactivates NovuHub accounts.
Both are switched on by environment variables on the server; nothing needs to change in the app.
OpenID Connect sign-in
1. Register NovuHub at your identity provider
Create a web application client and set the redirect URI to
https://<your-novuhub-host>/auth/oidc/callback
Request the scopes openid email profile. Copy the issuer URL, client ID and client secret.
2. Configure the server
NOVUHUB_OIDC_ISSUER=https://login.example.com/realms/acme # issuer, no trailing slash
NOVUHUB_OIDC_CLIENT_ID=novuhub
NOVUHUB_OIDC_CLIENT_SECRET=…
NOVUHUB_OIDC_SCOPES="openid email profile" # optional
NOVUHUB_OIDC_BUTTON_LABEL="Sign in with Acme SSO" # optional
NOVUHUB_OIDC_AUTO_PROVISION=1 # 1 = create an account on first sign-in (default), 0 = invite-only
Restart the service. The login page now shows the SSO button; /auth/oidc/login starts the flow.
How accounts are matched
- The IdP must return a verified email; the account with that email is signed in. With auto-provisioning on, an unknown email creates a new account (password-less — it can only sign in through SSO).
- The IdP subject (
sub) is stored as the account's external ID. - Deactivated accounts cannot sign in even when the IdP allows it.
- Existing password accounts keep working; SSO is additive.
Trust model
Tokens are exchanged server-to-server over TLS; the browser never sees them. The ID token's issuer, audience, expiry and nonce are checked, and the identity is read from the IdP's userinfo endpoint and cross-checked against the token's subject. State is bound to the session and expires after 10 minutes.
SCIM 2.0 provisioning
1. Configure the server
NOVUHUB_SCIM_TOKEN=$(python3 -c "import secrets;print(secrets.token_urlsafe(32))")
NOVUHUB_SCIM_DEFAULT_ROLE=member # role for provisioned people: member | lead | admin
2. Configure the identity provider
| Setting | Value |
|---|---|
| SCIM base URL | https://<your-novuhub-host>/scim/v2 |
| Authentication | Bearer token = NOVUHUB_SCIM_TOKEN |
| Unique identifier | userName (the email address) |
Supported endpoints: ServiceProviderConfig, Schemas, ResourceTypes, Users (list with filter=userName eq "…", create, get, replace, patch, delete) and an empty Groups list.
What provisioning does
| IdP action | NovuHub |
|---|---|
| Assign user | Creates the account (verified, password-less) and adds it to the company workspace with the default role |
| Update name / email | Updates the account |
active: false or unassign | Deactivates the account; it can no longer sign in, data is kept |
active: true | Reactivates |
| Delete | Deactivates (erasure stays a GDPR flow run by an administrator) |
Groups are not modelled; section access is managed inside NovuHub.
Testing the setup
- Sign in through the SSO button with a test user — a new account should appear in the Directory.
- In the IdP, run a provisioning test / Provision on demand for a user and check
GET /scim/v2/Users?filter=userName eq "user@example.com"returns it. - Deactivate the user in the IdP and confirm the login is refused.
Both features write to the server log (journalctl -u novuhub) on failure with the reason, without leaking tokens.