OAuth / SSO
QRY supports OAuth-based single sign-on against Google (Workspace / consumer accounts) and Microsoft Entra ID (Azure AD). Each QRY deployment is single-tenant: it talks to one Google project or one Azure tenant. If your users live in multiple directories, run multiple QRY instances.
You can have both providers enabled simultaneously — useful during a migration from one directory to another, or for organisations that bridge both. See Mixing providers below for the matching rules.
What you get
- One-click login — users click Continue with Microsoft (or Google) on the login page; no password to manage.
- Account auto-provisioning — users not yet in QRY are created on first login. Email is pre-verified.
- Stable identity — QRY stores the directory's immutable user id (Azure
oid, Googlesub), so user accounts survive email/UPN renames. - Optional AD group sync — map Entra security groups to QRY user groups; on every login QRY reconciles the user's memberships in the mapped groups. (Manually-assigned and domain-assigned memberships are never touched.)
What you don't get out of the box: SAML, Okta, Keycloak, multi-tenant Azure (/common, /organizations). If you need any of these, file an issue.
Configuring Microsoft Entra ID (Azure AD)
The setup requires admin access to the customer's Azure tenant.
1. Create an App Registration
- Open https://entra.microsoft.com → Identity → Applications → App registrations → New registration.
- Name it (e.g. "QRY — production").
- Supported account types: Accounts in this organizational directory only — Single tenant.
- Redirect URI (Web):
https://<your-qry-domain>/auth/azure/callback. For local dev:http://localhost:5173/auth/azure/callback. - Register.
2. Note the IDs
From the Overview tab:
- Application (client) ID — GUID.
- Directory (tenant) ID — GUID.
Both are needed in QRY's admin UI.
3. Create a client credential — secret OR certificate
Pick one. Most tenants accept secrets; some have policies that require certificate-based auth.
Option A — client secret (simpler). Certificates & secrets → Client secrets → New client secret. Copy the Value (not the Secret ID) immediately — it's only shown once.
Option B — certificate (some tenants require it). Generate a key pair and a self-signed cert (or use one issued by your CA). Quick and dirty:
openssl req -x509 -newkey rsa:2048 \
-keyout qry-key.pem -out qry-cert.pem \
-sha256 -days 730 -nodes \
-subj "/CN=qry-<tenant>"
Upload the public cert (qry-cert.pem) under Certificates & secrets → Certificates → Upload certificate. Keep the private key (qry-key.pem) safe — you'll paste it into QRY.
4. Grant API permissions
API permissions → Add a permission → Microsoft Graph → Delegated permissions. Add:
openidprofileemailUser.Read
Click Grant admin consent for <tenant>. (Requires your global admin rights — without consent, end users will see a per-user consent dialog instead.)
5. (Optional) Enable the groups claim
Required only if you plan to use AD group → QRY group mapping.
Token configuration → Add optional claim → ID → tick groups → choose Group ID → Add. If prompted to enable Microsoft Graph profile permissions, accept.
If a user belongs to more than ~200 security groups, Azure swaps the inline groups claim for a Graph API pointer. QRY doesn't follow that pointer in v1 — affected users will sign in fine, but their group memberships won't be synced. Logged as a warning. Contact us if this affects your deployment.
6. Configure QRY
Admin → System Configuration → OAuth / SSO:
| Field | Value |
|---|---|
| Enable OAuth | ON |
| Azure / Entra ID Application (client) ID | from step 2 |
| Azure / Entra ID Client Secret | the Value from step 3 (Option A only) |
| Azure / Entra ID Client Certificate (PEM) | concatenated qry-cert.pem + qry-key.pem (Option B only) |
| Azure / Entra ID Certificate Passphrase | only if the private key is encrypted |
| Azure / Entra ID Directory (tenant) ID | from step 2 |
| Azure / Entra ID Redirect URI | exactly the same string as in step 1 |
| Enable Azure group → QRY group mapping | ON only if step 5 done |
If both the secret and the certificate are filled, the certificate takes precedence. Leave the secret field blank when using cert auth.
Save. Log out. The login page now shows Continue with Microsoft.
Configuring Google
The Google flow is simpler.
- Google Cloud Console → APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Application type: Web application.
- Authorised redirect URI:
https://<your-qry-domain>/auth/google/callback. - Copy the Client ID and Client secret.
- In QRY: Admin → System Configuration → OAuth / SSO → fill the Google fields and save.
That's it. Google doesn't expose group claims by default, so AD-style group sync isn't available for Google.
How identity matching works
When a user clicks Continue with Microsoft / Google, QRY's flow:
- Match by stable directory id —
(oauth_provider, oauth_provider_id). For Azure that's theoid; for Google that's thesub. If found, log them in. - Fallback: match by email — if no provider-id match, look up by the email returned by the directory (Azure:
emailclaim →preferred_username→upn; Google:email). If found, the existing QRY user gets linked to the OAuth provider —oauth_provider_idis stamped on the row, no data lost. - Otherwise: create a new user — pre-verified, with display name from the directory, no password.
Step 2 is what makes migrations smooth: a user who used to log in with email/password (or with the other OAuth provider) and switches to Azure simply gets their existing QRY account linked. All workspaces, conversations, RBAC, and ABAC continue intact.
Mixing providers
If you run both Google and Azure on the same QRY deployment and the same person logs in with each provider in turn, QRY uses email as the bridge:
- First login (say Google): creates account
alice@acme.com, marksoauth_provider=google. - Same person later signs in with Azure (same email): step 1 fails (no Azure-specific id stored yet), step 2 matches by email, the row is updated to
oauth_provider=azure. All data preserved. - Switches back to Google: step 1 fails again (
oauth_provideris nowazure), step 2 matches by email, row becomesgoogleagain.
The flag flips on every alternation. The id and all linked data (workspaces, RBAC, conversations) stay stable. This works as long as both directories agree on the user's email. If a user's Google email is alice@oldco.com but their Azure UPN is alice@newco.com, QRY treats them as two different users (because that's what they look like).
If you need true multi-provider identity (one QRY user with both Google and Azure linked simultaneously), let us know — that's a separate model not built today.
AD group → QRY group mapping
Once Azure SSO is working and the groups claim is enabled, you can map specific Entra groups to existing QRY user groups.
Admin → System Configuration → OAuth Group Mappings. Click Add Mapping:
- Azure Group Object ID (GUID) — get this from Entra → Groups → (group) → Overview → Object ID.
- QRY Group — pick from the dropdown (manage QRY groups in Admin → Users & Groups).
Save. From the next login onwards, every user with that Azure group in their token gets added to the QRY group; users no longer in the Azure group get removed.
What's in scope of the sync
Only memberships in mapped QRY groups are reconciled. So if Alice is:
- in QRY group Analytics (mapped to an Azure group she's a member of) → kept.
- in QRY group Analytics (mapped to an Azure group she's no longer in) → removed on next login.
- in QRY group Pilot users (no mapping at all) → never touched.
- in QRY group @acme.com domain (assigned by domain-based auto-assignment) → never touched.
This means you can mix sources: AD-driven memberships for some groups, manual or domain-driven memberships for others. The sync only reconciles the AD-driven ones.
When changes propagate
Changes in Azure don't push to QRY — QRY pulls on each login. Practical implication: if you remove someone from a sensitive group in Azure, their QRY membership only disappears on their next OAuth login. For high-security removal, deactivate the user in QRY directly (their session and any new login are blocked instantly).
Common issues
AADSTS50011: Reply URL specified in the request does not match
The Redirect URI in QRY's admin config doesn't exactly match the one in the App Registration. Common gotchas: trailing slash, http vs https, port number. Copy-paste both and compare character by character.
Login works, but user lands on QRY as user role instead of admin.
Roles in QRY are not synced from Entra. The first user to log in is created as user regardless of their Entra role. Promote them in Admin → Users & Groups (you'll need an existing admin to do it — for the first admin, see Bootstrap admin below).
groups claim not present in the token.
Either the optional claim wasn't added in step 5, or the user isn't in any group. Decode the ID token at https://jwt.ms (Azure provides this tool) to confirm.
User has more than 200 group memberships.
Azure swaps the groups claim for a _claim_names.groups Graph pointer. QRY v1 logs a warning and treats the user as having no groups. Sync won't work for them until we add the Graph fallback.
Duplicate user rows after switching from Google to Azure.
Means the email in the directories doesn't match. Compare User.email in QRY with the email / preferred_username claim Azure returns. Either fix the directory entry or merge users manually (we can help — there's no built-in merge tool).
Bootstrap admin
For brand-new deployments where no admin exists yet: provisioning sets one local admin user automatically (see Multi-tenant provisioning). Use that account to log in once, promote your real admin (now an OAuth-created user), and disable the bootstrap account.
Security considerations
- Client secrets are stored in QRY's database, encrypted at rest with the same Fernet key as everything else (derived from
JWT_SECRET_KEY). They never appear in/api/auth/providers(which is anonymous) or in the frontend bundle. - Redirect URIs must be HTTPS in production. Azure permits
http://only forlocalhost. - Token rotation: rotate the Azure client secret (or the certificate) at least yearly. Update the value in QRY's admin UI; no service restart needed (admin saves trigger a config reload). Certificates have a hard expiry shown in the Entra portal — set a reminder before that date or logins will start failing with
AADSTS700027once the cert expires. - Granted admin consent is global — once you grant it, every user in the tenant can sign in without a per-user consent prompt. If you want a per-user prompt (rare), skip the Grant admin consent step.
- Group claim privacy: the
groupsclaim leaks the user's full group membership list (up to 200) to QRY. If your tenant has security-sensitive group names, weigh that — though QRY only logs group GUIDs, not names.
See also
- Users and groups — manage QRY-side groups that you map AD groups to.
- Multi-tenant provisioning — how new QRY deployments are bootstrapped per customer.
- License management — license limits apply to OAuth-created users the same as to local-auth users.