Single Sign-On & Directory Sync
Identity is where a governed AI platform starts: every budget, policy and audit record hangs off a person. AgentWatch integrates with the identity provider you already run rather than asking you to maintain a second user list.
Which protocols are supported?
Section titled “Which protocols are supported?”| Protocol | Support |
|---|---|
| SAML 2.0 | Native service provider — metadata exchange, signed assertions, certificate upload, IdP-initiated and SP-initiated flows |
| OIDC / OAuth 2.0 | Standard authorization-code flow against any compliant provider |
| LDAP / Active Directory | Direct bind with LDAPS and CA certificate upload |
| SCIM v2 | Per-organization provisioning and de-provisioning with managed tokens |
A unified hub-and-spoke setup wizard covers OIDC, SAML and LDAP, so the configuration path is the same shape whichever protocol you use. Providers are configured per organization, so each tenant in a multi-tenant deployment brings its own identity provider.
Does a first login create the user?
Section titled “Does a first login create the user?”That is your choice, per provider, and it is the single most consequential setting on this page.
- Auto-create on — the first successful login provisions the user, mints their gateway identity, and applies group-to-team mapping. Suitable when your identity provider is already the authority on who should have access.
- Auto-create off — the user lands in a pending state and no gateway key is issued until a tenant administrator approves them. Suitable when access must be granted deliberately.
An optional domain restriction narrows auto-creation to addresses on domains you own. Leaving it empty means anyone who can authenticate through that provider is provisioned, which is exactly right for some deployments and wrong for others — the field exists so that it is your decision rather than a default.
How do groups become teams?
Section titled “How do groups become teams?”Group-to-team mapping is unified across SAML, OIDC and LDAP, with three modes:
| Mode | Behavior |
|---|---|
| Off | Team is assigned by the default-team rule; groups are ignored |
| Automatic | A parse rule derives the team name from the group claim, so a new group creates a team without configuration |
| Mapped | An explicit table maps each group to a team, for when directory names and team names differ |
Team membership matters because team drives budget roll-ups, model-access scoping and every team filter in the console. A user with no team has no budget parent, so AgentWatch resolves a default team rather than leaving the field empty.
Roles can be assigned from the directory too, so an administrator in your IdP arrives as an administrator here.
What exactly does my IdP put in the group claim?
Section titled “What exactly does my IdP put in the group claim?”This is the question that decides whether mapping works, and there is no single answer — which is why AgentWatch will tell you rather than make you guess:
curl -s "https://<your-agentwatch-host>/admin/sso/observed-groups?providerId=<provider id>" \ -H "Authorization: Bearer <admin JWT>"It returns the exact values your tenant has actually received, each flagged as opaque or not, ready to paste into a mapping. A sign-in whose groups match nothing is recorded in the log together with this URL.
What the major directories send by default:
| Directory | Default groups claim |
Notes |
|---|---|---|
| Microsoft Entra ID | Group object IDs (GUIDs) — e.g. 8f4c…-…-… |
Display names only when the app registration is configured to emit them; sAMAccountName for on-premises synced groups |
| Okta | Group names | Configurable per app |
| Google Workspace | Group email addresses | |
| LDAP / AD | Full distinguished names (CN=Engineering,OU=…) |
Entra emitting GUIDs is the cause of most group-mapping trouble, and it produced two distinct symptoms before this was handled:
- Automatic mode derived team names with a rule that leaves a GUID untouched, creating teams named after GUIDs. It now skips opaque identifiers instead of naming a team after one, and records why.
- Mapped mode required an exact, case-sensitive match against the raw
claim, so typing
Engineeringagainst a GUID claim matched nothing, silently.
Which spellings of a group name will match?
Section titled “Which spellings of a group name will match?”Matching is shape-tolerant and case-insensitive, so you can enter whichever form you have in front of you. All of these map the same directory group:
| You enter | Matches a claim of |
|---|---|
Engineering |
Engineering, CN=Engineering,OU=Groups,DC=corp, CORP\Engineering |
CN=Engineering,OU=Groups,DC=corp |
the same, in any of those spellings |
CORP\Engineering |
the same |
8f4c1e2a-… (object ID) |
that group’s GUID claim |
It is deliberately not a substring match: Engineering does not match
Engineering-Contractors. A mapping that matches too much puts people in teams
they do not belong to, which is worse than one that matches nothing.
If you would rather map by name than by GUID, configure group claims in the Entra app registration to emit names instead — either approach works.
How do I verify group mapping before rolling it out?
Section titled “How do I verify group mapping before rolling it out?”- Sign one pilot user in through the IdP.
- Read back what the claim actually contained:
GET /admin/sso/observed-groups?providerId=<id> - Add a mapping for each group you care about (Settings → Single Sign-On → your provider → Group mapping), pasting a value from step 2.
- Sign the pilot user in again and confirm their team in Employee Management.
You should now see the user in the mapped team, and their spend rolling up to that team’s budget.
Group mapping: symptom → cause → fix
Section titled “Group mapping: symptom → cause → fix”| Symptom | Cause | Fix |
|---|---|---|
| Teams appear named after GUIDs | Automatic mode against an Entra groups claim |
Upgrade to 2.0.283+; opaque ids are skipped. Delete the GUID-named teams and use Mapped mode, or emit names from Entra |
| A mapping is configured and nothing matches | The claim carries GUIDs while the mapping holds display names (or vice versa) | GET /admin/sso/observed-groups and paste a real value |
| Everyone lands in one team | Mapping mode is Off, or the IdP sends no group claim at all | Set the mode, and confirm the claim is actually emitted |
| A user is in too many teams | A mapping value is a prefix of several group names | Use the full distinguished name or the object ID |
PUT /scim/v2/Groups/:id returned 404 |
Replace-style group updates were unimplemented before 2.0.283 | Upgrade |
| SCIM creates duplicate users | userName/externalId matched case-sensitively before 2.0.283 |
Upgrade; both are caseExact:false per RFC 7643 §7 |
How do I configure a provider?
Section titled “How do I configure a provider?”All three protocols follow the same path: Settings → Single Sign-On → Add provider, choose the protocol, paste the values from your identity provider, save as a draft, run the built-in test, then enable it. Nothing takes effect until you enable it, so a misconfigured provider cannot lock anyone out.
What your identity provider needs from AgentWatch
| Field | Value |
|---|---|
| ACS / Reply URL (SAML) | https://<your-agentwatch-host>/api/auth/saml/callback/<provider id> |
| Redirect URI (OIDC) | https://<your-agentwatch-host>/api/auth/oauth2/callback/<provider id> |
| Entity ID / Audience | the Entity ID you entered on the provider (SAML), shown on the provider row once saved |
SAML: what the “SAML Issuer (Entity ID)” field is
Section titled “SAML: what the “SAML Issuer (Entity ID)” field is”The SAML Issuer (Entity ID) you enter on a SAML provider is your identity
provider’s entity ID — the value Entra ID, Okta or ADFS uses to identify
itself (for Entra, https://sts.windows.net/{tenant}/). If you upload your
IdP’s metadata XML, AgentWatch fills it in for you from the document’s
entityID attribute.
It is not AgentWatch’s own entity ID. AgentWatch’s entity ID — the one your IdP needs in its SP configuration — is always:
https://<your-agentwatch-host>/api/auth/saml/metadata/<provider id>which is exactly what the metadata endpoint at that URL publishes. Nothing to set, and nothing to change on upgrade.
My SAML metadata says http:// — how do I fix it?
Section titled “My SAML metadata says http:// — how do I fix it?”If the Entity ID and ACS URL AgentWatch publishes begin with http:// while
your users reach it over HTTPS, your reverse proxy is terminating TLS without
telling AgentWatch. The connection AgentWatch sees is plain HTTP, so that is
what it advertises — and most identity providers refuse to register a
non-HTTPS Assertion Consumer Service URL, because SAML 2.0 expects assertions
to be protected in transit.
Check what your deployment publishes:
curl -s https://<your-agentwatch-host>/api/auth/saml/metadata/<provider id> | grep entityIDThere are two fixes. Prefer the first — it is one line of proxy configuration and needs nothing per-deployment.
1. Have the proxy declare the scheme. From 2.0.301 AgentWatch honours both the standards-track RFC 7239 header and the de-facto one. Set it on the frontend that terminates TLS, not on a port-80 frontend that only redirects — a redirect-only frontend never reaches the application, so the header there has no effect:
frontend https-in bind *:443 ssl crt /etc/pki/tls/certs/your.pem mode http http-request set-header X-Forwarded-Proto httpslocation / { proxy_pass http://agentwatch:8787; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme;}Use set-header rather than add-header: it replaces any value the caller
sent, so a client cannot spoof the scheme. Reload the proxy and re-run the
curl above — the entity ID should now begin https://.
2. Or pin it in AgentWatch. If you cannot change the proxy, set
AW_PUBLIC_BASE_URL (below). It takes precedence over every header, so no
request can influence it.
| Symptom | Cause | Fix |
|---|---|---|
Metadata shows http://, users reach HTTPS |
Proxy sends neither Forwarded nor X-Forwarded-Proto |
Set the header on the TLS frontend, or set AW_PUBLIC_BASE_URL |
| Header set but nothing changed | It is on a redirect-only (port 80) frontend | Move it to the frontend with bind *:443 ssl |
| IdP rejects the ACS URL | It is not HTTPS | As above, then re-upload or re-enter the metadata at the IdP |
| Entity ID changed after fixing this | The published entity ID now carries https |
Update the Entity ID registered at your IdP to match the metadata |
Set your public URL
Section titled “Set your public URL”Set AW_PUBLIC_BASE_URL to the externally reachable URL of your deployment,
without a trailing slash:
AW_PUBLIC_BASE_URL=https://agentwatch.example.comWhen it is unset, the SP metadata document and the ACS URL are built from the
Host header of whatever request asked for them. That header is set by the
caller, so metadata fetched through an unexpected hostname advertises that
hostname — and SP metadata is a document administrators hand directly to their
IdP. Setting this pins both values to your real address regardless of how the
request arrived. It is not mandatory, because requiring it would break existing
deployments on the one path where a wrong answer locks everybody out; AgentWatch
logs a one-time saml-public-base-unset warning instead.
Verify it took effect:
curl -s https://<your-agentwatch-host>/api/auth/saml/metadata/<provider id> | grep entityIDThe entityID and the Location of the AssertionConsumerService should both
name your public URL.
Over the API, for infrastructure-as-code:
curl -s -X POST https://<your-agentwatch-host>/api/admin/oauth2/providers \ -H "Authorization: Bearer <tenant-admin JWT>" \ -H 'Content-Type: application/json' \ -d '{ "name": "Okta", "protocol": "saml", "samlIssuer": "http://www.okta.com/exk…", "samlEntryPoint": "https://acme.okta.com/app/…/sso/saml", "samlCertificate": "-----BEGIN CERTIFICATE-----\nMIID…", "autoCreateUsers": true, "domainRestriction": "acme.com", "groupMappingMode": "mapped", "defaultRole": "user", "enabled": true }'For OIDC, send "protocol": "oidc" with issuerUrl, clientId, clientSecret
and scopes. For LDAP, use the LDAP provider form — host, bind DN, base DN,
LDAPS and an uploaded CA certificate.
How do I test it before rolling it out?
Section titled “How do I test it before rolling it out?”- Save the provider as a draft (leave
enabledfalse). - Use Test connection on the provider row — it performs a real bind or metadata fetch and reports the actual error, not a generic failure.
- Sign in from a private browser window with one pilot account.
- Check Employee Management: the user should exist, with the expected team and role, and a gateway key if auto-create is on.
- Enable the provider for everyone.
How do I set up SCIM?
Section titled “How do I set up SCIM?”- Settings → Single Sign-On → SCIM, generate a token for the organization.
- In your identity provider, set the SCIM base URL to
https://<your-agentwatch-host>/scim/v2and paste the token as the bearer credential. - Push a test user and confirm they appear; then push a group and confirm the team is created or mapped.
- De-provision that test user in your directory and confirm their key stops authenticating — this is the half most rollouts forget to verify.
Tokens are per organization and revocable from the same screen, so a leaked token is contained to one tenant.
Common problems
Section titled “Common problems”| Symptom | Cause | Fix |
|---|---|---|
| Login loops back to the sign-in page | ACS/redirect URL mismatch | Make the value in your IdP match the provider row exactly, including the provider id |
Invalid signature on SAML |
Wrong or stale certificate | Re-upload the current IdP signing certificate |
| User signs in but has no access | autoCreateUsers is off and they are pending |
Approve them in Employee Management, or enable auto-create |
| User signs in but API calls return 401 | They have a login but no gateway identity | Provision them, or see delegated identity |
| Everyone lands in one team | Group mapping is off, or the claim is missing | Set the mapping mode and confirm your IdP actually sends the group claim |
| Mapping is configured but nothing matches | The claim carries object IDs (GUIDs) while the mapping holds display names — Entra’s default | GET /admin/sso/observed-groups and paste a value your IdP actually sent |
| Teams appear named after GUIDs | Automatic mode against an Entra group claim | Upgrade to 2.0.283+, then map explicitly or emit names from Entra |
| Nobody can sign in after enabling | Domain restriction excludes their address | Widen or clear the domain restriction |
No account matching <address> exists in this organization |
The address in your IdP token is not the one the employee was provisioned under | Compare it with the Contact column in Employee Management — see below |
Email not verified by your IdP… assert email_verified=true |
A release earlier than 2.0.293 | Upgrade. Entra omits the email claim for accounts without a mailbox, and before 2.0.293 that skipped the provisioned-user check entirely |
SCIM employees show — in the Team column |
Created before 2.0.293, which fixed only new employees | Upgrade to 2.0.304+ — a one-time sweep at startup fills them in |
Provisioning fails with Unsupported PATCH path: <attribute> |
An attribute in your IdP mapping that AgentWatch has no field for (job title, department, employee ID) | Upgrade to 2.0.306+; before that, remove the attribute from the mapping |
| A user’s details changed in the IdP but not in AgentWatch | The same thing — one unsupported attribute rejected the whole update, so the changes beside it never applied | Upgrade to 2.0.306+; Entra retries failed users on the next cycle (~40 min). If provisioning is quarantined it will not resume on its own — use Restart provisioning |
Why does one account sign in and another not?
Section titled “Why does one account sign in and another not?”Almost always because the two accounts carry different email claims, not because one of them is configured differently in AgentWatch.
Microsoft Entra fills the email claim from the account’s mail attribute.
A normal user has a mailbox, so the claim is present. A privileged or
administrative account — the a- prefixed accounts many organisations use —
often has no mailbox, so Entra sends no email claim and falls back to the
UPN. If the UPN is spelled differently from the address SCIM provisioned
(a-mark.deckert@example.com versus a-mdeckert@example.com), sign-in is
refused even though the employee is plainly listed in Employee Management.
The refusal message names the address we searched for. Compare it against the Contact column for that employee:
- They match — the account is not in this organization. Add it, or check you are signing in to the right tenant.
- They differ — this is the cause. Either correct the employee’s email to
the UPN the IdP actually sends, or have the IdP send the provisioned address
(in Entra, add
emailas an optional claim under App registration → Token configuration, or populate the account’s mail attribute).
email_verified is not the cause, despite what releases before 2.0.304 said.
Entra work accounts do not send that claim at all, and AgentWatch does not
require it for an account that already exists.
What does SCIM provisioning cover?
Section titled “What does SCIM provisioning cover?”SCIM v2 handles the full lifecycle without a login: users created, updated, suspended and de-provisioned from your identity provider, and group membership synchronized to teams. Each organization gets its own SCIM token, managed and revocable from the console, so a token leak is contained to one tenant.
De-provisioning is the half that matters most: a user disabled in your directory loses gateway access, and their API key stops authenticating.
Can employees get gateway access without ever logging in?
Section titled “Can employees get gateway access without ever logging in?”Yes, and for most deployments that is the point. An employee’s gateway identity — the ak_ key their applications use — is independent of console sign-in. Employees can be created by SSO, SCIM, CSV import, the employee wizard, or automatically by a service key (see delegated identity), and a person can hold a governed gateway identity with no usable console password at all.
What if we have no identity provider?
Section titled “What if we have no identity provider?”Local accounts work, with password policy, lockout after repeated failures, forced password change, and administrator-driven resets. Organizations commonly start local and add SSO later; existing users are matched by email address when a provider is introduced, so adopting SSO does not duplicate anyone.
Sources and references
Section titled “Sources and references”- What a provisioned employee can then do: Delegated identity and Auto Create Users.
- Roles, audit and session policy: Security and compliance.
- Identity providers in the wider integration list: Integrations.
- Enrolling devices rather than users: Endpoint enrollment.