Single sign-on and SCIM
Single sign-on lets people sign in to Qualor through your identity provider (IdP), with OIDC or SAML 2.0. Accounts are created at the first sign-in or linked to existing ones, IdP groups can grant roles, and password sign-in can be limited to break-glass admins. With SCIM, the IdP creates, updates and deactivates the accounts for you.
Single sign-on needs a licence key listing sso. The Business plan allows one enabled connection
at a time. Several identity providers at once need sso.multi
(One connection or several), and SCIM needs scim: both are in the
Enterprise plan only (Plans and prices). Without single
sign-on, people sign in with a user name and password. API and CI tokens work the same either way.
Requirements
- A licence key listing
sso, applied as in Apply a key. QUALOR_PUBLIC_URLset to the address people open Qualor at (server settings). The redirect URI, the SAML entity id and the ACS URL are made from it, so a connection cannot be enabled without it (409PUBLIC_URL_REQUIRED). If you changeQUALOR_PUBLIC_URLlater, give the IdP the new values.- An IdP the server can reach over
https. An IdP on your internal network, or one on plainhttp(a test Keycloak), must be listed inQUALOR_SSO_INTERNAL_HOSTS: comma-separated host names, each with an optional port, such askeycloak.corp.example.comorkeycloak:8080. A private CA needsNODE_EXTRA_CA_CERTS, as for GitLab.
Only the server talks to the IdP: to the OIDC issuer you entered and the token, JWKS and userinfo endpoints its discovery document names, or to a SAML metadata URL when an admin asks Qualor to read it. People’s browsers go to the IdP’s sign-in page; Qualor’s server never calls it.
Connections live in Settings → Single sign-on (instance admins). Choose Add an OpenID Connect connection or Add a SAML connection, fill in the form and Save. A saved connection shows Values for the identity provider to copy, Check the connection with Test (and Read metadata for SAML), and its Group mappings. Edit opens it again; Delete the connection removes it.
The Keycloak steps below are tested automatically, against Keycloak 26.7.4. The Entra ID, Okta and Google Workspace steps are not yet tested with real accounts.
Add an OIDC connection
Qualor uses the authorization code flow with PKCE, a nonce and a state, and checks the ID token’s signature, issuer, audience, expiry and issue time. It needs a confidential client: a client id and a client secret.
Copy this value into the IdP, from Values for the identity provider:
| Qualor’s label | IdP asks for | Value |
|---|---|---|
| Redirect URI | redirect URI (callback, reply URL) | https://qualor.example.com/api/v0/ee/sso/oidc/<connection id>/callback |
Then fill in New OpenID Connect connection in Qualor:
| Field | What to enter |
|---|---|
| Name | the button text: “Sign in with” and this name |
| Issuer URL | the issuer exactly as the IdP’s discovery document names it, a trailing slash included. Qualor keeps it as entered, and /.well-known/openid-configuration below it must name the same issuer, character for character |
| Client id, Client secret | from the IdP. The secret is stored encrypted and never shown again: the form says Set. It is never shown again., and Change replaces it. A changed issuer needs the secret again |
| Client authentication | client_secret_basic (the default) or client_secret_post |
| Scopes | openid email profile by default; openid is required |
| Also read the userinfo endpoint | off by default. Turn it on for an IdP that sends the email or groups only there |
| Username claim, Email claim, Display name claim, Groups claim | where to read each: preferred_username, email, name, and groups once groups come from claims. Leave one empty to not read it. The username claim must be one only the IdP’s administrator can change, never the user (why) |
The Accounts part of the form is the same for OIDC and SAML:
- Create an account at the first sign-in and Link to an existing account with the same verified email (never an instance administrator): see Who gets an account.
- Required claims (optional): one
claim=valuea line, at most 5, such ashd=example.com. A sign-in without that exact value is refused (required_claim). - Groups come from: see Group mappings and sync.
Press Test under Check the connection. It reads the discovery document and the JWKS, and lists the Authorization endpoint (the page people’s browsers open) and the Token endpoint, JWKS and Userinfo endpoint that Qualor’s server will call (“The test passed.”). Then tick Enabled: show it on the sign-in page and Save.
Emails: Qualor stores an OIDC email, and links by it, only when the IdP marks it verified
(email_verified: true, the JSON boolean). An unverified email is never stored, and never used
to link an account. An IdP that does not send email_verified gives accounts without an email;
SCIM fills them in.
Keycloak
- In your realm, Clients → Create client: type OpenID Connect, a client id such as
qualor, Client authentication on, Standard flow only. - Valid redirect URIs: the redirect URI above.
- For groups: in the client’s dedicated scope, add a Group Membership mapper, token claim name
groups, Full group path off, added to the ID token. - In Qualor: issuer
https://<keycloak host>/realms/<realm>, the client id, and the secret from the client’s Credentials tab. Keycloak sendspreferred_username,email,email_verifiedandname.
Microsoft Entra ID
- App registrations → New registration, platform Web, redirect URI as above. Under Certificates & secrets, create a client secret.
- Issuer:
https://login.microsoftonline.com/<tenant id>/v2.0, with your tenant’s id. The multi-tenantcommonandorganizationsendpoints and Azure AD B2C are not supported: their discovery document names an issuer other than the URL it is read from, which Qualor refuses. - The username claim is
preferred_username(the user principal name). - Groups: Token configuration → Add groups claim; the claim is
groups. Entra sends each group’s object id, not its name, so map the object ids in Qualor. A person in very many groups gets no groups claim at all (Entra points to Microsoft Graph instead), so prefer Groups assigned to the application.
Okta
- Applications → Create App Integration, OIDC, Web Application, Sign-in redirect URI as above, grant type Authorization Code.
- Issuer: your Okta domain (
https://<your domain>.okta.com) for the org authorization server, or the custom authorization server’s issuer (https://<your domain>.okta.com/oauth2/default), as its discovery document shows it. - Groups: on the app’s Sign On tab, set a Groups claim filter named
groups(for example “Starts withqualor-”), and addgroupsto the scopes in Qualor. Okta sends group names.
Google Workspace
- In the Google Cloud console, APIs & Services → Credentials → Create OAuth client ID, type Web application, with the redirect URI as an authorised redirect URI.
- Issuer:
https://accounts.google.com. - Any Google account can sign in to such a client, so add
hd=example.com(your Workspace domain) under Required claims (optional). - Google sends no groups claim. To give everyone the same starting role, set Groups come from
to The groups claim at each sign-in and add a
*group mapping (Group mappings). Or manage memberships by hand.
Add a SAML connection
Qualor uses SAML 2.0 Web SSO: requests by HTTP-Redirect, responses by HTTP-POST. Copy these values into the IdP, from Values for the identity provider:
| Qualor’s label | IdP asks for | Value |
|---|---|---|
| ACS URL | ACS URL (reply URL, single sign-on URL) | https://qualor.example.com/api/v0/ee/sso/saml/<connection id>/acs |
| Entity id (SP) | SP entity id (identifier, audience URI) | https://qualor.example.com/api/v0/ee/sso/saml/<connection id>/metadata |
| SP metadata URL | SP metadata, for IdPs that import it | the same URL: it serves Qualor’s metadata XML |
Then give Qualor the IdP’s side in New SAML connection, from its metadata URL or by hand. For the metadata URL, save the connection first, then press Read metadata. Qualor reads it once, never on a schedule, and shows What the metadata would change: the entity id, the SSO URL and the Signing certificates with their SHA-256 fingerprints. Nothing is saved until you press Save these values (or Discard). The metadata document itself is not signed, so check the fingerprints with the IdP first.
| Field | What to enter |
|---|---|
| Identity provider entity id | the IdP’s issuer. Every response and assertion must name it |
| SSO URL (HTTP-Redirect) | the IdP’s single sign-on URL. It may carry a query, such as Google’s ?idpid=… |
| Metadata URL (optional) | may carry a query too, such as Entra ID’s ?appid=… |
| Signing certificates (PEM, 1 to 3) | several while the IdP rotates its key: RSA of 2048 bits or more, or EC. Once saved, each is listed with its SHA-256 fingerprint and “valid until” date. An Expired certificate still works: IdPs often sign with expired self-signed ones |
| NameID format | Persistent (recommended), Email address or Unspecified |
| Username claim, Email claim, Display name claim, Groups claim | the attribute names (email and displayName by default). An empty username uses the NameID |
| This identity provider verifies the email attribute | off by default. SAML has no “verified” flag, so tick it only if the IdP’s email attribute is one it verified. Without it, the email is not stored, and not used to link |
| Also require a signature on the whole response | off by default. The assertion itself must always be signed |
The Accounts fields are those of OIDC.
Send a username attribute. Without one, the NameID becomes the Qualor user name. Keycloak’s
persistent NameID, for example, is an opaque G-<uuid>, so a new account would be called
G-3f2a…. Name the IdP’s username attribute in Username claim instead, and pick one only the
IdP’s administrators can change (why).
Groups: the whole sign-in, groups included, must fit in 16 KiB, and more than 1 000 group
values are refused. That is roughly 350 Entra group ids, fewer with long group names. A sign-in
over the limit is refused (invalid_response), never cut short. Send only the groups Qualor needs:
filter them at the IdP.
SHA-256 or stronger is required. Qualor refuses responses signed or digested with SHA-1, and comments, DOCTYPEs and processing instructions anywhere in a response. If your IdP signs with SHA-1, switch it:
| IdP | Setting |
|---|---|
| Keycloak | the client’s Signature algorithm: RSA_SHA256 |
| Entra ID | SAML Signing Certificate → Signing Algorithm: SHA-256 |
| Okta | Signature Algorithm: RSA_SHA256 |
| AD FS | the relying party trust’s Advanced → Secure hash algorithm: SHA-256 |
The service provider key (optional). By default Qualor’s requests are unsigned and it refuses encrypted assertions; TLS protects them. To sign requests and accept encrypted assertions, make a key pair and paste both parts under Service provider key (optional), as Private key and Certificate:
openssl req -x509 -newkey rsa:3072 -nodes -keyout sp.key -out sp.crt -days 3650 -subj "/CN=qualor-sp"
The key must be an unencrypted PKCS#8 RSA key of 2048 to 4096 bits, which is what this command writes. It is stored encrypted and never shown again. The SP metadata then offers the certificate, so the IdP can encrypt to it.
Keycloak (SAML)
- Clients → Create client, type SAML, Client ID = the SP entity id; Valid redirect URIs and Assertion Consumer Service POST Binding URL = the ACS URL.
- Sign assertions on, Signature algorithm
RSA_SHA256, Name ID formatpersistent. - Mappers (in the client’s dedicated scope): a User Property
email→ attributeemail, a User Propertyusername→ attributeusername, a User PropertyfirstName(or a full name) → attributedisplayName, and a Group list → attributegroupswith Full group path off. - In Qualor: metadata URL
https://<keycloak host>/realms/<realm>/protocol/saml/descriptor, and the attributesusername,email,displayName,groups.
The automated test covers these steps except the username mapper. Add it anyway: without it,
accounts are named after the NameID (G-<uuid>).
Microsoft Entra ID (SAML)
- Enterprise applications → New application → Create your own application (non-gallery), then Single sign-on → SAML: Identifier = the SP entity id, Reply URL = the ACS URL.
- Unique User Identifier (Name ID):
user.objectidwith the format Persistent, so the subject survives a renamed user principal name. - Attribute names for Qualor: email
http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress, usernamehttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, display namehttp://schemas.microsoft.com/identity/claims/displayname, groups (after Add a group claim)http://schemas.microsoft.com/ws/2008/06/identity/claims/groups. Groups arrive as object ids. - Metadata URL: the App Federation Metadata Url, which ends in
?appid=….
Okta (SAML)
- Applications → Create App Integration → SAML 2.0: Single sign-on URL = the ACS URL, Audience URI (SP Entity ID) = the SP entity id, Name ID format Persistent.
- Attribute statements:
username→user.login,email→user.email,displayName→user.displayName. Group attribute statements:groups, with a filter such as “Starts withqualor-”. - Metadata URL: the app’s Metadata URL on its Sign On tab.
Google Workspace (SAML)
In the Admin console, Apps → Web and mobile apps → Add custom SAML app: ACS URL and
Entity ID as above, then map Primary email to an attribute email. Google’s SSO URL
carries ?idpid=…, which Qualor accepts. Its NameID is the primary email, so set Qualor’s
NameID format to Email address.
Who gets an account
At each sign-in Qualor looks for the account in this order:
- The known link: the IdP’s subject (OIDC
sub, SAML NameID) on this connection, linked before. Once linked, the email and user name no longer matter: a person whose email changes at the IdP keeps their Qualor account. - A SCIM record of this connection that has not signed in yet, matched by its SCIM user name
against the sign-in’s username claim. Without a username claim, OIDC falls back to the email,
only when the IdP marks it verified, and SAML to the NameID. A SCIM record of an instance admin
is never matched this way: that sign-in is refused (
no_account), and the admin links the account under Settings → Linked accounts instead. - A verified email, only when the connection has Link to an existing account with the same
verified email ticked (
linkByEmail, off by default). The IdP must have verified the email, exactly one active Qualor user may have it, and that user must not be an instance admin. Qualor never links by user name, and never by an unverified email. - A new account (just-in-time), when the connection has Create an account at the first
sign-in ticked (
jit, on by default). The user name comes from the username claim, else the email’s local part. If it is taken,-2to-20is appended; an existing account of that name is never used. The new account has no password, no role until a group mapping or an org admin gives one, and an email only when it is verified and nobody else has it. - Otherwise the sign-in is refused (
no_account).
A new account whose verified email already belongs to another Qualor user is refused
(email_in_use) rather than created twice. That person signs in to their existing account with its
password and links it under Settings → Linked accounts, or an admin turns on linking by
verified email.
Choose a username claim the user cannot change
Step 2 gives the account SCIM made for a name to whoever signs in with that name. So the username claim (or SAML attribute) must be one only the IdP’s administrator can change, never the user: Entra ID’s user principal name, Okta’s login, or Keycloak’s username with editing turned off.
An email never matches unverified. A username claim or attribute that is the email counts only
when the email is verified (OIDC email_verified; SAML only with This identity provider verifies
the email attribute ticked), and so does a SAML NameID of the Email address format. Don’t
point the username claim at the email claim unless the IdP verifies every email.
Linking by email trusts every connection
An account’s email can come from any enabled connection or its SCIM, and linking looks at every
account, whichever connection gave it its email. Say a partner’s IdP gives one of its users
ceo@corp.example, so that user’s Qualor account gets the email. When the real owner of the
address first signs in through a connection that links by email, they are linked to the partner
user’s account, with its access. Turn Link to an existing account with the same verified email
on only when every connection, and its SCIM, is trusted to assert emails.
Linked accounts and sessions
Every user can see, link and unlink their own linked accounts in Settings → Linked accounts. An instance admin is never linked by email: they sign in with their password and link the connection there. Two refusals keep people from locking themselves out:
- 409
SCIM_MANAGED_IDENTITY: a link the IdP made through SCIM can be removed only by an instance admin (DELETE /api/v0/ee/sso/users/<user id>/identities/<id>). - 409
LAST_SIGN_IN_METHOD: the link is the user’s last way to sign in. A password counts only if the current password policy lets this user sign in with it (Password sign-in); the emergency switch does not count. Another link counts only on a connection in effect (One connection or several). A user without a password cannot set one themselves, because changing a password needs the current one. An instance admin sets one first with Reset password on the user’s row in Settings → Users, which ends that user’s sessions and revokes their personal tokens.
A password an admin set. When an admin sets a user’s password, the user must choose their own at their next password sign-in. A single sign-on sign-in does not ask for it, because that session did not use the password; the next password sign-in still does.
A single sign-on session lasts QUALOR_SESSION_TTL_HOURS, like any other. Signing out of Qualor
ends the Qualor session only, and signing out of the IdP does not end it (no single logout).
Deactivating a user, in Qualor or through SCIM, ends every session at once.
Group mappings and sync
Each connection’s Groups come from (groupSource in the API) is one of:
- Nowhere: no group mappings, the default: memberships are managed by hand.
- The groups claim at each sign-in (
claims): sync runs at each sign-in. A sign-in whose groups claim is missing counts as no groups, so only*mappings apply to it. - SCIM provisioning (
scim): sync runs at each SCIM change.
The connection’s Group mappings map a Group to a Role in an Organization, for the Whole organization or one Project. Add a row, then Save mappings. The roles are:
- in an organisation: Organization admin (
admin), Project admin, Maintainer (member) or Viewer; - on one project: Project admin, Maintainer or Viewer.
Every mapping needs only sso: roles themselves need no licence
(Roles and the audit log). A mapping never makes anyone an instance admin.
The group value is matched exactly, case included: a claim value, or a SCIM group’s external id,
else its display name. The value * matches everyone who signs in through the connection, a
default membership. A connection holds up to 500 mappings.
Sync gives each person the strongest mapped role per organisation and per project, and owns only the memberships it created:
- sync changes or removes a membership it created when the groups change;
- it never touches a membership an org admin made by hand, or another connection’s;
- Settings → Members marks sync’s organisation memberships “From SSO group sync: connection”. Project grants made by sync are not marked;
- an org admin who changes such a membership’s role by hand takes it over: it becomes a manual membership and sync leaves it alone. Removing one by hand works too, but the next sync adds it back while the group still maps;
- the organisation’s last Organization admin is kept: sync never removes or demotes it, and
writes
sync kept the last adminto the server log instead; - a project that already has 1 000 roles granted directly on it gets no new grant from sync, which
writes
sync skipped a project at its grant limitto the server log instead.
Deleting a connection deletes its account links, mappings and SCIM tokens. People who sign in only
through it cannot sign in until an admin sets them a password. The memberships its sync granted
stay, as manual ones. While password sign-in is limited to break-glass admins, the last enabled
connection cannot be disabled or deleted (409 LAST_SSO_CONNECTION). Set Password sign-in
back to Everyone with a password first, or, on the Enterprise plan, enable another connection
first.
One connection or several
On the Enterprise plan (a key listing sso.multi), every enabled connection is in effect: each
is a button on the sign-in page, up to 10 connections. On the Business plan (sso without
sso.multi), one connection signs people in at a time:
- You can store up to 10 connections, but enable only one. A new connection is created disabled, and Enabled: show it on the sign-in page cannot be ticked while another connection is enabled. You can still prepare it fully: fill it in, give the IdP its values, press Test or Read metadata, and add its group mappings.
- Creating a connection enabled, or enabling one, while another is enabled is refused with 409
SSO_MULTI_NOT_LICENSED, and nothing is saved. - Everything else (editing any connection, testing it, its mappings, deleting it) works as on the Enterprise plan.
Settings → Single sign-on notes the Business plan’s limit under its title, and marks an enabled
connection that does not sign people in Not in effect. On the Enterprise plan it counts the
connections (“N of 10 connections”). In the API, each connection has inEffect: true when it
signs people in.
Switch identity provider on the Business plan
Nothing is deleted when you switch, so you can switch back the same way.
- In Settings → Single sign-on, add the new connection and save it. It stays disabled while the old connection is enabled.
- Prepare it: copy its values into the new IdP, press Test (and Read metadata for SAML), and add its group mappings. It is not on the sign-in page yet.
- If Settings → Sign-in has Password sign-in set to Only the break-glass
administrators, set it to Everyone with a password for the switch. Otherwise disabling the
old connection, the last enabled one, is refused (409
LAST_SSO_CONNECTION; the page says “Set password sign-in back to everyone first (Settings → Sign-in).”). - To keep people’s existing accounts rather than create new ones, tick Link to an existing account with the same verified email (never an instance administrator) on the new connection. Accounts are linked to the old connection only, so at the first sign-in through the new one Qualor finds them as in Who gets an account.
- Edit the old connection, untick Enabled: show it on the sign-in page, and Save.
- Straight away, Edit the new connection, tick Enabled: show it on the sign-in page, and Save. Between steps 5 and 6 nobody can sign in with single sign-on. Check that the sign-in page shows the new connection, and sign in with it.
- If you changed it in step 3, set Password sign-in back to Only the break-glass administrators.
Instance admins link their accounts by hand under Settings → Linked accounts once the new connection is enabled. The old connection keeps its linked accounts, mappings and SCIM tokens.
On the Enterprise plan, enable the new connection first and disable the old one when you are ready; both work in between.
After a move to the Business plan
When sso.multi goes (a Business key replaces an Enterprise one, or the feature lapses), nothing is
deleted, disabled or changed. Every connection keeps its Enabled setting, its configuration,
linked accounts, mappings and SCIM tokens. But only the oldest enabled connection (the first
one created) is in effect. The other enabled connections leave the sign-in page, and Settings →
Single sign-on marks them Not in effect. A sign-in through one of them, even one started
before the change, ends with unavailable (“This sign-in method is not available.”); the server
log’s detail is oidc.not_in_effect or saml.not_in_effect.
- To choose which connection works, disable the one in effect: the next oldest enabled connection takes over at once. You cannot enable the first one again while another is enabled.
- People whose accounts are linked only to a connection not in effect cannot sign in with single sign-on. They can sign in with a password if the password sign-in setting lets them. Otherwise make their connection the one in effect, or set them a password (Reset password on the user’s row in Settings → Users).
- Unlinking counts only links on the connection in effect (409
LAST_SIGN_IN_METHOD). - SCIM tokens of a connection not in effect keep working while
scimis licensed. The Business plan does not listscim, so SCIM stops there anyway (When the licence lapses).
With an Enterprise key again, every enabled connection is in effect at once.
Password sign-in and break-glass admins
Settings → Sign-in (instance admins; PUT /api/v0/ee/sso/settings) chooses who may sign in
with a password, under Password sign-in: Everyone with a password (everyone, the
default), or Only the break-glass administrators (break_glass_only). Pick the Break-glass
administrators: up to 10 instance admins who are active and have a password. Saving “Only the
break-glass administrators” needs at least one such admin and at least one enabled connection
(otherwise 422 VALIDATION_FAILED, naming what is missing). While this setting is stored, the last
enabled connection cannot be disabled or deleted (409 LAST_SSO_CONNECTION). If you are not in
the list yourself, the page warns you that you will sign in with SSO from then on.
With break-glass admins only:
- the sign-in page shows the single sign-on buttons, with the password form folded under Emergency administrator sign-in;
- anyone else who types a correct password gets the same answer as a wrong one (401
INVALID_CREDENTIALS), so the answer never tells whether the password was right. The audit log records a failed sign-in with the reasonpassword_disabled; - API and CI tokens keep working unchanged, and users who have a password can still change it;
- Forgot password? follows the same rule: only the break-glass admins get a reset link, the page answers everyone else the same way, and the link is checked again when it is used;
- Settings → Users sends no invitations (409
PASSWORD_SIGN_IN_DISABLED): new users sign in with single sign-on; - an instance admin can still set a user’s password, which the user can use once the policy allows. Setting it ends that user’s sessions and revokes their personal tokens.
Keeping a break-glass admin. While the stored setting is “break-glass admins only”, Qualor
refuses to deactivate or demote the last listed admin who could still use a password (409
LAST_BREAK_GLASS_ADMIN; through SCIM, 400 mutability). This guard follows the stored
setting, even while the emergency switch is on or the licence has lapsed, so nobody is locked out
once the switch is removed or the licence renewed. To remove the last one, set password sign-in
back to everyone first.
If your identity provider is down
QUALOR_FORCE_PASSWORD_SIGN_IN=true is the emergency switch. It re-enables password sign-in for
every user who has a password, whatever Settings → Sign-in says.
- Set it, and restart the server. With the Compose file of
Install the server, add
QUALOR_FORCE_PASSWORD_SIGN_IN=trueto.envand rundocker compose up -d server; that file passes the variable from.envto the server. A Compose file of your own that does not pass it needsQUALOR_FORCE_PASSWORD_SIGN_IN: 'true'under the server’senvironment:instead. With Helm, setconfig.forcePasswordSignIn: trueand runhelm upgrade. - Sign in with a password and fix the connection or the IdP.
- Remove the variable, and restart again.
While it is set:
- the server logs a warning at every start:
password sign-in forced by QUALOR_FORCE_PASSWORD_SIGN_IN; - with the audit log licensed, each start records
auth.password_sign_in_forcedwith the stored policy, and each password sign-in that only the switch allowed is recorded as a sign-in markedforced: true; - instance admins see a banner on every page that asks them to remove it once single sign-on works again. Settings → Sign-in says so too, and that its own setting takes effect again once the variable is removed and the server restarted.
Any value other than true, false or empty stops the server at start, with a message naming the
variable. Sessions made while it was set stay valid after it is removed, until they expire.
SCIM with Entra ID and Okta
SCIM needs the Enterprise plan (a key listing scim); on the Business plan every SCIM request
answers 403 FEATURE_NOT_LICENSED. With SCIM, the IdP creates, updates, deactivates and deletes
Qualor accounts, and pushes groups. SCIM belongs to one single sign-on connection: its accounts
sign in through that connection.
The base URL, shown as SCIM base URL in Settings → SCIM (IdPs call it the tenant URL):
https://qualor.example.com/api/v0/ee/scim/v2
The token. In Settings → SCIM, press New token in the connection’s panel, enter a
Token name (and, if you want, Expires on (optional)), and press Create token. The
SCIM token looks like qlr_scim_… and is shown once, in Your new SCIM token: copy it,
then press Done. Qualor stores only its hash; the list shows how it Starts with and when it
was Last used. A connection has at most 5 active tokens (409 SCIM_TOKEN_LIMIT_REACHED). A
token has no expiry unless you set one. Revoke a token when you replace it. The IdP sends it as
Authorization: Bearer …. Qualor’s Gitleaks rule (qualor-token) finds qlr_scim_ tokens like
the other qlr_ tokens, so a leaked one is reported as a secret.
- Entra ID: in the enterprise application, Provisioning → Automatic: Tenant URL = the base URL, Secret Token = the token. Press Test Connection, map the attributes (below) and start provisioning.
- Okta: in the app’s General tab, enable SCIM provisioning. Then, in Provisioning →
Integration: SCIM connector base URL = the base URL, Unique identifier field for users
userName, Authentication Mode HTTP Header, and the token as the Bearer value. Enable Create, Update and Deactivate Users, and push groups with Push Groups.
The Entra ID and Okta SCIM steps are not yet tested with real accounts.
What Qualor keeps of a user: userName (unique on the connection), externalId, the name
parts, displayName, the email and active. The email is the primary one, else the first work
one, else the first; a SCIM email counts as verified. Everything else, passwords and the enterprise
extension included, is accepted and ignored. SCIM owns these values: the IdP may overwrite a
change made in Qualor. Settings → Users says so on SCIM users, which carry a SCIM badge.
What Qualor understands:
- Filters: one
eqcomparison, onuserName,externalId,emails.value(oremails[type eq "work"].value) andidfor users, and ondisplayName,externalIdandidfor groups. Anything else,and,orand other operators included, answers 400invalidFilter. Pages hold at most 100. - PATCH:
add,replaceandremovein any case (Entra sendsReplace), with or without apath. Booleans astrue/falseor as the strings"True"/"False"(Entra’s form). User paths such asactive,userName,displayName,name.givenNameandemails[type eq "work"].value. Groupmembersadded or removed as a list, or removed bymembers[value eq "<id>"](Entra’s form). Extension attributes are ignored. - Not supported: bulk operations, sorting, ETags and password changes. The service provider configuration says so.
Deactivation (active: false) ends every session of the person and revokes every
personal token they have, in the same step. Reactivating does not bring the tokens back. Their
memberships stay, so a reactivated person gets their access back. SCIM reactivation also
overrides a deactivation an admin made in Qualor: the IdP is the source of truth for SCIM
users, so deactivate them at the IdP.
Delete (DELETE /Users/{id}) deactivates the person the same way, removes them from the
connection’s SCIM groups, and removes the SCIM link. The Qualor user row stays, deactivated,
since analyses, issue changes and audit events name it. Creating the same person again with the
same email is then refused with 409 uniqueness, because the old account still has that email.
Change the old account’s email, or reactivate it in Settings → Users and turn on linking by
verified email, so the new SCIM user links to it.
SCIM never deactivates or deletes the last active instance admin, or the last break-glass admin
while password sign-in is limited (400 mutability), and never makes anyone an instance admin.
Groups pushed by SCIM feed the connection’s group mappings when its Groups come from is SCIM provisioning (Group mappings and sync). Every change of a group’s members syncs those people at once.
Limits. A connection’s SCIM service accepts 1 200 requests a minute per token (then 429 with
Retry-After), request bodies of at most 1 MiB, and application/scim+json or
application/json. An address that sends 60 requests with a wrong or missing token in a minute
gets 429 instead of 401 for further wrong tokens until the minute ends. A valid token from that
address still works, so a script with a revoked token cannot stop your IdP. Behind a reverse proxy,
set QUALOR_TRUST_PROXY (Install the server) so Qualor counts each client’s
own address, not the proxy’s. A request that collides with another change in the database changes
nothing and gets 503 with Retry-After; the IdP retries it.
Limits and rate limits
| What | Limit |
|---|---|
| Starting a sign-in | 30 a minute per address |
| Linking an account | 10 a minute per user |
| The OIDC callback, the SAML ACS, and the SAML finish step | 60 a minute per address each |
GET /api/v0/auth/methods |
60 a minute per address |
| SCIM | 1 200 requests a minute per token; 60 failed authentications a minute per address |
| Connections, mappings, break-glass admins, SCIM tokens | 10 connections, 500 mappings per connection, 10 break-glass admins, 5 active SCIM tokens per connection |
A browser sign-in beyond its limit ends on the sign-in page with “Too many sign-in attempts. Wait a
minute and try again.” (rate_limited).
When the licence lapses
Nothing is deleted or rewritten, and nobody is locked out for good.
After sso lapses:
- the sign-in page shows no single sign-on button, and every single sign-on route answers 403
FEATURE_NOT_LICENSED; - password sign-in is open to everyone who has a password, whatever Settings → Sign-in says;
- users without a password cannot sign in until an instance admin sets one with Reset password on their row in Settings → Users (the No password filter lists them). They choose their own at the next sign-in. Setting a password ends that user’s sessions and revokes their personal tokens, so their CI jobs need a new token;
- existing sessions stay valid until they expire, and API and CI tokens keep working;
- memberships from group sync stay as they are, and nothing syncs until the renewal;
- connections, links, mappings and the sign-in setting are kept, and a renewed key restores them.
After scim lapses:
- every SCIM request answers 403
FEATURE_NOT_LICENSED, and the IdP reports provisioning failures; - deprovisioning stops: a person removed at the IdP keeps their Qualor account and tokens until an admin deactivates them in Settings → Users;
- tokens, SCIM users and groups are kept, and a renewed key resumes provisioning.
After sso.multi lapses, or a Business key replaces an Enterprise one, only the oldest enabled
connection signs people in, and nothing is deleted
(After a move to the Business plan).
What Qualor does not do
- Single logout, OIDC or SAML: signing out of Qualor does not sign you out of the IdP, nor the reverse.
- IdP-initiated SAML (starting from the IdP’s app launcher): every SAML response must answer a request Qualor sent. Start from Qualor’s sign-in page.
- SCIM bulk operations, sorting and ETags.
- Entra ID’s multi-tenant
commonissuer and Azure AD B2C,private_key_jwtclient authentication, encrypted ID tokens, a connection per organisation, or making anyone an instance admin from a group.
See Troubleshooting for every sign-in error and Webhooks and REST API for the API.