Single sign-on and SCIM

Single sign-on (OIDC and SAML 2.0) needs an enterprise licence listing sso; SCIM provisioning needs one listing scim. Licence keys are not on sale yet (Enterprise), so everything below describes how these features work once a key can be applied. Without them, people sign in with a user name and password, as they do today in both editions, and API and CI tokens work the same with or without single sign-on.

What it adds and what it needs

  • Sign-in through your identity provider (IdP). An instance admin adds one or more connections (at most 10), each OIDC or SAML. Each enabled connection is a button on the sign-in page: “Sign in with name”.
  • Accounts on first sign-in (just-in-time), or linked to an existing account (see Who gets an account).
  • Group mappings: IdP groups become organisation or project roles, and are kept in step at each sign-in or SCIM change.
  • Password sign-in for break-glass admins only, if you want it, with an emergency switch for the day the IdP is down.
  • SCIM 2.0, so Entra ID or Okta create, update and deactivate Qualor accounts for you.

It needs:

  1. A licence key listing sso (and scim for provisioning), applied as in Enterprise.
  2. QUALOR_PUBLIC_URL set 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 (409 PUBLIC_URL_REQUIRED). If you change QUALOR_PUBLIC_URL later, give the IdP the new values.
  3. An IdP the server can reach over https. An IdP on your internal network, or one on plain http (a test Keycloak), must be listed in QUALOR_SSO_INTERNAL_HOSTS: comma-separated host names, each with an optional port, such as keycloak.corp.example.com or keycloak:8080. A private CA needs NODE_EXTRA_CA_CERTS, as for GitLab.

Only the server talks to the IdP, and only 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. The browser goes to the IdP’s sign-in page; Qualor never calls it.

Connections live in Settings → Single sign-on (instance admins): Add an OpenID Connect connection or Add a SAML connection, fill in the form and Save. Once saved, the 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.

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

And fill in New OpenID Connect connection in Qualor:

Field What to enter
Name the button text: the sign-in button says “Sign in with” and this name
Issuer URL the issuer exactly as the IdP’s discovery document names it. Qualor keeps it exactly as entered, a trailing slash included, and requires /.well-known/openid-configuration below it to name the same issuer, character for character
Client id, Client secret from the IdP. The secret is stored encrypted and never shown again (Set. It is never shown again.; 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; the groups claim is groups once groups come from claims). Leave one empty to not read it. The username claim matches accounts SCIM made (see Who gets an account), so it must be one only the IdP’s administrator can change, never the user: Entra ID’s UPN, Okta’s login, Keycloak’s username with editing turned off. Don’t point it at the email claim unless the IdP verifies every email; Qualor uses it for matching only when that sign-in’s email is verified (email_verified)

The Accounts part of the form is the same for both protocols: Create an account at the first sign-in, Link to an existing account with the same verified email (never an instance administrator), Required claims (optional) (one claim=value a line, at most 5, such as hd=example.com), and Groups come from (see Group mappings).

Press Test under Check the connection: it reads the discovery document and the JWKS now 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 and links an OIDC email 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 Qualor accounts without an email; SCIM (below) fills them in.

Keycloak

Tested automatically, against Keycloak 26.7.4.

  1. In your realm, Clients → Create client: type OpenID Connect, a client id such as qualor, Client authentication on, Standard flow only.
  2. Valid redirect URIs: the redirect URI above.
  3. 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.
  4. In Qualor: issuer https://<keycloak host>/realms/<realm>, the client id, and the secret from the client’s Credentials tab. Keycloak sends preferred_username, email, email_verified and name.

Microsoft Entra ID

Not yet tested with a real account.

  1. App registrations → New registration, platform Web, redirect URI as above. Under Certificates & secrets, create a client secret.
  2. Issuer: https://login.microsoftonline.com/<tenant id>/v2.0, with your tenant’s id. The multi-tenant common (and organizations) endpoints and Azure AD B2C are not supported yet: their discovery document names an issuer other than the URL it is read from, which Qualor refuses.
  3. The username claim is preferred_username (the user principal name).
  4. Groups: Token configuration → Add groups claim. 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 from Entra (it points to Microsoft Graph instead), so prefer Groups assigned to the application. The claim is groups.

Okta

Not yet tested with a real account.

  1. Applications → Create App Integration, OIDC, Web Application; Sign-in redirect URI as above; grant type Authorization Code.
  2. 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.
  3. Groups: on the app’s Sign On tab, set a Groups claim filter named groups (for example “Starts with qualor-”), and add groups to the scopes in Qualor. Okta sends group names.

Google Workspace

Not yet tested with a real account.

  1. Google Cloud console, APIs & Services → Credentials → Create OAuth client ID, type Web application, with the redirect URI as an authorised redirect URI.
  2. Issuer: https://accounts.google.com.
  3. Any Google account can sign in to such a client, so add hd=example.com (your Workspace domain) under Required claims (optional): a sign-in without that exact value is refused (required_claim). A connection takes up to 5 required claims.
  4. 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 (see 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, either from its metadata URL or by hand. For the metadata URL, save it first, then press Read metadata: Qualor reads it once (never on a schedule) and shows What the metadata would change, with 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); check the fingerprints with the IdP first, because the metadata document itself is not signed.

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, because 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 above.

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. Pick one that only the IdP’s administrators can change (Entra ID’s user principal name, Okta’s login, Keycloak’s username with editing turned off), because Qualor uses it to match accounts provisioned by SCIM (see Who gets an account).

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, because a cut would drop access at random. 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: in Keycloak, the client’s Signature algorithm RSA_SHA256; in Entra ID, SAML Signing Certificate → Signing Algorithm SHA-256; in Okta, Signature Algorithm RSA_SHA256; in 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 under Service provider key (optional), 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 (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)

Qualor’s automated test signs in through a Keycloak 26.7.4 SAML client with signed assertions, RSA_SHA256, a persistent NameID and the email, displayName and groups mappers, but no username mapper: it checks that the account is then named after the NameID (G-<uuid>). The username mapper of step 3 is not part of that test; add it anyway, so accounts get readable names.

  1. Clients → Create client, type SAML, Client ID = the SP entity id; Valid redirect URIs and Assertion Consumer Service POST Binding URL = the ACS URL.
  2. Sign assertions on, Signature algorithm RSA_SHA256, Name ID format persistent.
  3. Mappers (in the client’s dedicated scope): a User Property email → attribute email, a User Property username → attribute username, a User Property firstName (or a full name) → attribute displayName, and a Group list → attribute groups with Full group path off.
  4. In Qualor: metadata URL https://<keycloak host>/realms/<realm>/protocol/saml/descriptor, and the attributes username, email, displayName, groups.

Microsoft Entra ID (SAML)

Not yet tested with a real account.

  1. 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.
  2. Unique User Identifier (Name ID): user.objectid with the format Persistent, so the subject survives a renamed user principal name.
  3. Attribute names for Qualor: email http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress, username http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, display name http://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.
  4. Metadata URL: the App Federation Metadata Url, which ends in ?appid=….

Okta (SAML)

Not yet tested with a real account.

  1. 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.
  2. Attribute statements: username → user.login, email → user.email, displayName → user.displayName. Group attribute statements: groups, with a filter such as “Starts with qualor-”.
  3. Metadata URL: the app’s Metadata URL on its Sign On tab.

Google Workspace (SAML)

Not yet tested with a real account. 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:

  1. 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.
  2. 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. The username claim (or attribute) must be one only the IdP’s administrator can change, since whoever can set it can take over the account SCIM made for that name. An email never matches unverified: a username claim or attribute that is the email one 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. So don’t point the username claim at the email claim unless the IdP verifies every email. 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.
  3. A verified email, only when the connection has Link to an existing account with the same verified email ticked (linkByEmail, off by default): the email must be verified by the IdP, 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.
  4. 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, -2 to -20 is appended, and 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.
  5. 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 a second time. 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.

Linking by email trusts every connection. An account’s email can come from any enabled connection (a verified email at sign-in) or from its SCIM, and linking looks at every account, whichever connection gave it its email. If a less trusted IdP (a partner’s tenant, say) gives one of its own users ceo@corp.example, the Qualor account of that user gets the email; when the real owner of that address first signs in through a connection with linking by email on, 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.

Instance admins link by hand. An instance admin is never linked by email: they sign in with their password and use Settings → Linked accounts to link the connection. Every user can see and unlink their own linked accounts there. Two refusals keep people from locking themselves out:

  • 409 SCIM_MANAGED_IDENTITY: an account 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 last way this user can sign in. A password counts only if the current password policy lets this user sign in with it (see Password sign-in), and another link counts only on an enabled connection. A user without a password cannot set one themselves (changing a password needs the current one): ask an instance admin to set one first (Settings → Users → Reset a password), which ends that user’s sessions and revokes their personal tokens. It counts only while password sign-in is allowed for them. The emergency switch below does not count.

Signing out of Qualor ends the Qualor session only: Qualor does no single logout, and signing out of the IdP does not end a Qualor session. A session lasts QUALOR_SESSION_TTL_HOURS, like any other. 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 Nowhere: no group mappings (the default: memberships are managed by hand), The groups claim at each sign-in (claims), or SCIM provisioning (scim). The connection’s Group mappings then map a Group to a Role in an Organization, for the Whole organization or one Project (Add a row, then Save mappings):

  • in an organisation: Organization admin (admin), Project admin, Maintainer (member) or Viewer;
  • or on one project: Project admin, Maintainer or Viewer.

These are the labels with rbac licensed; without it the page names the roles Admin, Project admin, Member and Viewer, and offers only Admin and Member for the whole organisation.

Project admin, Viewer and every project mapping need a licence listing rbac too (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:

  • a membership sync created is changed or removed by sync when the groups change;
  • a membership an org admin made by hand, or another connection’s, is never touched;
  • Settings → Members marks sync’s organisation memberships “From SSO group sync: connection” (project grants made by sync are not marked yet);
  • an org admin who changes such a membership’s role by hand takes it over: from then on it is 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 Admin is kept: sync never removes or demotes it, and writes sync kept the last admin to the server log instead;
  • while rbac is not licensed, mappings to Project admin, Viewer or a project are skipped.

With groups from claims, sync runs at each sign-in; with SCIM, at each SCIM change. A sign-in whose groups claim is missing counts as no groups, so only * mappings apply to it.

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, or enable another connection, first.

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 must be active and have a password (someone without one cannot be picked). Saving “Only the break-glass administrators” needs at least one such admin and at least one enabled connection (422 VALIDATION_FAILED otherwise, naming what is missing). Afterwards the last enabled connection cannot be disabled or deleted while this setting is stored (409 LAST_SSO_CONNECTION). If you are not in the list yourself, the page warns: “You are not a break-glass administrator: you will sign in with SSO from now 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 it as a failed sign-in with the reason password_disabled;
  • API and CI tokens keep working unchanged, and so does changing your own password (for users who have one);
  • an instance admin can still set a user’s password; the user can use it 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 says “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, a 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 re-enables password sign-in for every user who has a password, whatever Settings → Sign-in says. It is the emergency switch:

  1. Set it, and restart the server. With the Compose file of Install the server, add QUALOR_FORCE_PASSWORD_SIGN_IN=true to .env and run docker compose up -d server; that file passes the variable from .env to the server. A Compose file of your own that does not pass it needs QUALOR_FORCE_PASSWORD_SIGN_IN: 'true' under the server’s environment: instead. With Helm, set config.forcePasswordSignIn: true and run helm upgrade.
  2. Sign in with a password and fix the connection (or the IdP).
  3. Remove the variable, and restart again.

While it is set:

  • the server writes 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_forced, with the stored policy; each password sign-in that only the switch allowed is recorded as a sign-in marked forced: true;
  • instance admins see a banner on every page: “Password sign-in is forced on by QUALOR_FORCE_PASSWORD_SIGN_IN. Remove it once single sign-on works again.” Settings → Sign-in says so too, and that its 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 lets the IdP create, update, deactivate and delete Qualor accounts, and push groups. It belongs to one SSO connection: its accounts sign in through that connection.

The base URL (the “tenant URL”), shown as SCIM base URL in Settings → SCIM:

https://qualor.example.com/api/v0/ee/scim/v2

The token. In Settings → SCIM, in the connection’s section, give a Token name (and optionally Expires on (optional)) and press Create token. The SCIM token looks like qlr_scim_…, is shown once, and is stored only as a hash; the list then shows only 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 may have an expiry (none by default); revoke one when you replace it (Revoke). Qualor’s Gitleaks rule (qualor-token) finds qlr_scim_ tokens like the other qlr_ tokens, so a leaked one is reported as a secret. The IdP sends it as Authorization: Bearer ….

  • Entra ID: in the enterprise application, Provisioning → Automatic: Tenant URL = the base URL, Secret Token = the token; Test Connection, then map the attributes (below) and start provisioning. Not yet tested with a real account.
  • Okta: in the app’s General tab enable SCIM provisioning, then Provisioning → Integration: SCIM connector base URL = the base URL, Unique identifier field for users userName, Authentication Mode HTTP Header, the token as the Bearer value; enable Create, Update and Deactivate Users; push groups with Push Groups. Not yet tested with a real account.

What Qualor keeps of a user: userName (unique on the connection), externalId, the name parts, displayName, the email (the primary one, else the first work one, else the first; a SCIM email counts as verified) and active. Everything else, including passwords and the enterprise extension, is accepted and ignored. SCIM owns these values: the IdP may overwrite a change made in Qualor (Settings → Users says so on SCIM users, with a SCIM badge).

What Qualor understands:

  • Filters: one eq comparison: userName, externalId, emails.value (or emails[type eq "work"].value) and id for users; displayName, externalId and id for groups. Anything else, and, or and other operators included, is 400 invalidFilter. Pages of at most 100.
  • PATCH: add, replace and remove in any case (Entra sends Replace), with or without a path; booleans as true/false or as the strings "True"/"False" (Entra’s form); user paths such as active, userName, displayName, name.givenName, emails[type eq "work"].value; group members added or removed as a list, or removed by members[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) signs the person out everywhere (every session ends) 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, because 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: groups pushed by SCIM are matched by their external id, else their display name, against the connection’s group mappings, when its Groups come from is SCIM provisioning. Every change of a group’s members syncs those people at once.

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 its further wrong tokens for the rest of that minute; a valid token from the same address still works, so another client behind the same address (a script with a revoked token, say) 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 in Settings → Users → Reset a password (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; 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.

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 common issuer and Azure AD B2C, private_key_jwt client 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.