Claude Desktop & Cowork
Claude Desktop and Cowork can authenticate to AIControls with each user's own work account instead of a shared credential. The app runs the sign-in flow against your identity provider, then presents the resulting token on every request. AIControls validates it and attributes the request to that person — so audit, budgets, and model policy all work per-user, with your IdP's MFA and conditional-access rules applying at sign-in.
Why this instead of an access token
Handing every user a personal access token also works, and is simpler to set up. Gateway SSO is worth the extra configuration when you want:
- No credential to distribute or rotate. Users sign in with the account they already have.
- IdP-enforced access. MFA, conditional access, and device compliance are evaluated at sign-in. Revoking someone in the IdP cuts off gateway access on their next token refresh.
- Attribution without an enrollment step. The first request from a new user is already attributed.
Prerequisites
- Claude Desktop 1.6889.0 or later (earlier versions do not support gateway SSO)
- Ability to register an application in your identity provider
- Ability to deploy a managed configuration (MDM) to your users' machines
- AIControls admin access, to reach Settings → SSO/OIDC
How it works
Claude Desktop runs the OIDC authorization-code flow with PKCE in the user's browser, against your identity provider. It then sends the resulting token, unchanged, as Authorization: Bearer <token> on every request to AIControls.
AIControls acts as a stateless resource server. It never participates in the sign-in flow and never issues a token of its own. On each request it:
- Fetches your identity provider's signing keys (discovered from the issuer, then cached).
- Verifies the token's signature and expiry.
- Verifies the token's issuer and audience both match what you configured.
That third step is what makes the configuration below more than boilerplate. A token's signature only proves your identity provider minted it — not that it was minted for AIControls. Without an audience check, a token issued to any other application in your tenant would be accepted here, and that application could replay it. AIControls will not let this be left out by omission: a provider with no audience configured logs a warning naming itself at startup and rejects every token at request time. The proxy still boots — so an upgrade cannot be taken down by a configuration that predates the requirement — but that provider authenticates nobody until it is corrected.
Step 1 — Register the application with your IdP
- Microsoft Entra ID
- Google Workspace
- Okta
Create a public client app registration for Claude Desktop:
- In the Entra admin center, go to App registrations → New registration.
- Under Redirect URI, choose the Mobile and desktop applications platform and add
http://127.0.0.1/callback. - Under API permissions, ensure the delegated scopes
openid,profile,email, andoffline_accessare granted. (offline_accessis what returns a refresh token — see Token refresh.) - Copy the Application (client) ID and the Directory (tenant) ID — you need both below.
Use 127.0.0.1, not localhost — most IdPs do not treat them as interchangeable (RFC 8252 §7.3).
Keep the /callback path. Entra wildcards the port for loopback redirect URIs but, despite what Microsoft's documentation says, does not wildcard the path in practice: http://127.0.0.1/callback matches whichever port the app picks, while http://127.0.0.1 alone fails with AADSTS50011.
Register a public client, not a Web or confidential client. Claude is a public PKCE client and sends no client secret. A confidential registration rejects the token exchange with Token exchange failed (HTTP 401).
To include group membership in the token (needed if your model-access policies or budgets are group-scoped), go to Token configuration → Add groups claim and choose the group types to emit.
Optionally add an email claim. Entra omits email from ID tokens unless you ask for it: Token configuration → Add optional claim → ID → email. AIControls works without it — see Step 4 — but adding it keeps user identifiers readable in the audit log.
Google cannot be used for this flow. Google's token endpoint requires client_secret on the authorization-code exchange for every client type, including Desktop app (installed application) clients. Claude Desktop signs in as a public PKCE client and sends no secret, so the exchange fails with:
Token exchange failed (HTTP 400)
{"error": "invalid_request", "error_description": "client_secret is missing."}
This is not a client-type or scope problem and cannot be configured around — creating a Desktop app client does not help, because Google demands the secret from those too.
What to do instead, if Google is your directory:
-
Use the gateway-as-Authorization-Server path (recommended for Google) — point the client's OIDC settings at AIControls itself rather than at Google. AIControls is an authorization server: the client signs in through it, AIControls performs the Google exchange server-side where the client secret is available, and returns a token it minted itself. So the public-client restriction never applies. One Google client serves both the developer portal and the gateway, and Google's refresh caveat stops mattering because AIControls issues the gateway token.
Full field-by-field setup: Gateway as the authorization server.
This works with any OIDC provider, not just Google, and is the general answer whenever an identity provider will not support a public PKCE client.
-
Use Microsoft Entra ID or Okta for this flow. Both support genuine public PKCE clients that send no secret.
-
Use access tokens for these users instead of interactive sign-in — see Claude Code.
Your existing Google client still works for the developer portal: AIControls performs that exchange server-side with the secret, so portal sign-in and the tokens it issues are unaffected.
Create a Native application for Claude Desktop:
- In the Okta admin console, go to Applications → Create App Integration, choose OIDC — OpenID Connect, then Native Application.
- Set the Sign-in redirect URI to
http://127.0.0.1:<port>/callback, using the exact port you will pin withredirectPortin the managed configuration below (for example8123). - Ensure Authorization Code with PKCE is the grant type, and that no client secret is issued.
- Copy the Client ID.
Okta requires an exact redirect URI match, including the port — it does not wildcard the port the way Entra does. Pin the port in the managed configuration with redirectPort, and register that same port here. If they disagree, sign-in fails at the redirect.
Your issuer is the authorization server's issuer value, for example https://YOUR_ORG.okta.com/oauth2/default. Use that value itself — not the /.well-known/openid-configuration metadata URL.
To include group membership in the token, add a groups claim to the authorization server's token settings and scope it to the groups you want emitted.
Step 2 — Choose a token type
The app can send either kind of token. This choice determines the audience value you configure in Step 3.
| Mode | What the app sends | Audience value | When to choose it |
|---|---|---|---|
id_token (default) | The OIDC ID token | The Claude Desktop client ID | Start here. Nothing extra to register. |
access_token | An OAuth access token | Your gateway's API scope / resource identifier | Your organization requires access tokens for API calls, or you want gateway access to be a separately consentable scope. |
In access_token mode the client must also be configured with scopes set to the gateway's registered API scope — it is required in that mode, not optional.
On Entra ID that means your gateway's own app registration must Expose an API with a custom scope, and the client's registration must be granted that scope as a delegated permission. Missing either half fails sign-in with AADSTS65001. You cannot use a Microsoft Graph access token: Graph tokens are not validatable by a third party.
access_token mode is also the way around the Google Workspace refresh caveat below.
On Entra ID in access_token mode, set "accessTokenAcceptedVersion": 2 in your API app registration's manifest. Version 1 access tokens are issued with a different issuer value (https://sts.windows.net/TENANT/) than the one your v2 issuer URL advertises, so validation rejects them. If you cannot change the manifest, add a second provider entry using the v1 issuer.
Step 3 — Configure AIControls
Gateway SSO reuses your SSO / OIDC identity provider configuration. How much you need to configure depends on which app registration the client uses.
Nobody generates a Client ID here. Your identity provider mints it when you create the app registration in Step 1; you paste that same value into the client's configuration and, if it has its own registration, into AIControls.
The client signs in as a public client with PKCE and no client secret, so it needs an app registration of that type. Only Microsoft Entra ID lets one registration serve both the portal and this flow. Google (Web vs Desktop app) and Okta (Web vs Native) fix the client type at creation, so on those providers a dedicated registration is required — see Step 1.
- Its own registration (required on Google and Okta)
- Reusing the portal's registration (Entra only)
- Go to Settings → SSO / OIDC.
- If it is not configured yet, choose your provider and enter the Tenant ID (Entra) or Okta domain, plus the Client ID of your portal application. Toggle Enabled.
- Under AI client sign-in, enter the client's Client ID in Client ID of the AI app's own registration. Add more than one if several apps have their own registrations.
- Click Save. Changes apply immediately — no restart.
The portal's own Client ID stays accepted; these are additive. That is what lets one identity provider configuration serve both the builder portal and the gateway.
Entra ID only. If you point the client at the same app registration the portal uses, its token's audience is already the portal Client ID — so AIControls needs no additional configuration at all. Leave the Client ID field empty.
The cost is on the IdP side: that registration must also permit a public-client loopback flow, which means adding a Mobile and desktop applications redirect URI of http://127.0.0.1/callback and enabling Allow public client flows on an app that is otherwise confidential.
This is not available on Google or Okta, where a client's type is fixed when it is created.
Many security policies disallow enabling public-client flows on a confidential app registration, and Microsoft's guidance is to keep client types separate. Prefer a dedicated registration unless you have a reason not to.
Only list applications you control. Every entry widens which tokens the gateway accepts, and any application whose audience appears here can present its users' tokens to your gateway.
One identity provider for both Claude Code and Claude Desktop
This is the usual arrangement, and everything above already covers it: a single directory and a single issuer serving both.
- Claude Code users sign in to the developer portal against that provider and use the access token it issues.
- Claude Desktop signs in interactively against the same provider and presents its token straight to the gateway.
The only value you add is the AI client's own Client ID, in Step 3 — or nothing at all if it reuses the portal's registration. You do not need a second provider entry.
One identity provider is all AIControls accepts for gateway authentication. If you genuinely need a second directory trusted at the same time — during an acquisition or a migration between identity providers — configure the extra issuers in identity.oidcProviders (see Identity provider fields), or run a separate workspace per directory.
Step 4 — Set the user identifier claim
AIControls attributes each request to a principal, and prefers the user's email address because that is what personal access tokens, portal accounts, and budget assignments key on. When the token has no email claim, it falls back to the stable user identifier.
On Microsoft Entra ID, set the User ID Claim to oid. Entra's sub claim is pairwise: it differs per app registration, so the same person authenticating through a different application appears as a different principal. oid is the tenant-wide immutable user object ID.
- In Settings → SSO / OIDC → Advanced, set User ID Claim to
oidfor Entra ID, or leave it assubfor Okta and other providers. - Set Groups Claim (in the main section above) if your provider emits groups under a non-standard name —
groupsis the default.
Group membership is passed through unchanged, so existing group-scoped model policies and budgets apply to gateway-SSO users with no further configuration.
Step 5 — Decide whether to auto-provision users
Skip this step unless you use team-scoped model access.
Team membership lives on the portal user record, and someone who signs in only through Claude Desktop never visits the portal, so they have no record. Such a user resolves to the global model ceiling rather than their team's — meaning team-scoped model restrictions do not apply to them. They are not blocked; they are governed by the wrong ceiling.
To close that gap, turn on Auto-Provision Users (in Gateway SSO (advanced), or per-issuer on the Gateway Issuers tab). The first request from a new user then creates a builder-role portal record whose teams are derived from the token's groups via your team group bindings (Settings → Teams), falling back to the default team.
Existing users are never modified, so any teams or roles you have assigned by hand survive later sign-ins.
Users appear on the Identities page (Inventory → Identities) either way — that listing comes from request traffic. This setting is specifically about the portal record that carries team membership.
Step 6 — Deploy the managed configuration
Claude Desktop reads gateway SSO settings from managed configuration (MDM). Two keys matter:
inferenceCredentialKind— set to"interactive"to select SSO mode.inferenceGatewayOidc— a JSON string (not a nested object) holding the OIDC settings.
- Microsoft Entra ID
- Google Workspace
- Okta
{
"inferenceCredentialKind": "interactive",
"inferenceGatewayOidc": "{\"issuer\":\"https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0\",\"clientId\":\"YOUR_CLAUDE_DESKTOP_CLIENT_ID\",\"scopes\":[\"openid\",\"profile\",\"email\",\"offline_access\"],\"bearerTokenType\":\"id_token\"}"
}
Google cannot be named as the issuer here — it requires a client secret that a public PKCE client cannot send (see Step 1). Point the OIDC settings at AIControls instead:
{
"inferenceCredentialKind": "interactive",
"inferenceGatewayOidc": "{\"issuer\":\"https://YOUR_GATEWAY\",\"clientId\":\"oc_YOUR_REGISTERED_CLIENT\",\"scopes\":[\"mcp\"],\"bearerTokenType\":\"access_token\",\"redirectPort\":8123}"
}
bearerTokenType must be access_token, and clientId comes from dynamic client registration rather than from Google. See Gateway as the authorization server for the whole setup.
Do not omit inferenceGatewayOidc entirely — some builds validate the block and reject a partially filled or empty one, so configure it as above rather than clearing it.
{
"inferenceCredentialKind": "interactive",
"inferenceGatewayOidc": "{\"issuer\":\"https://YOUR_ORG.okta.com/oauth2/default\",\"clientId\":\"YOUR_CLAUDE_DESKTOP_CLIENT_ID\",\"scopes\":[\"openid\",\"profile\",\"email\",\"offline_access\"],\"bearerTokenType\":\"id_token\",\"redirectPort\":8123}"
}
Pin redirectPort to the port you registered in Step 1 — Okta requires the redirect URI to match exactly.
Also point the app at your gateway URL, which you can find in Settings → Integrations.
Gateway as the authorization server
Use this when your identity provider will not issue tokens to a public PKCE client — Google always, and any provider that refuses secret-less clients. Instead of the app signing in against your IdP directly, it signs in against AIControls, which runs the IdP exchange server-side with the portal client's secret and mints its own token.
Everything in this section replaces Step 1 and Step 6. Steps 3 through 5 still apply.
Register the gateway's own callback
Because AIControls performs the IdP exchange itself here, it is the party your identity provider redirects back to — at a path of its own, separate from the one the admin console uses:
https://YOUR_GATEWAY/oauth/sso/callback
YOUR_GATEWAY here is the gateway's own public URL — the same value clients use as their Issuer URL, shown as Proxy URL on the SSO / OIDC tab. It is not the address of the admin console, which on a default install is served on a different port and can be a different hostname entirely.
Add that as an additional redirect URI on the same IdP client whose Client ID you entered under Settings → SSO / OIDC, alongside the /auth/callback value already in the Redirect URL field. Both are needed: /auth/callback for portal sign-in, /oauth/sso/callback for AI clients signing in through the gateway. This path is fixed and not configurable.
The Redirect URL field must also be filled in, even though this flow does not use its value: leaving it empty means there is no browser callback for the consent page to redirect to, so the Sign in with SSO button does not appear at all.
Every provider allows more than one redirect URI per client:
- Google Workspace
- Microsoft Entra ID
- Okta
APIs & Services → Credentials → your Web application client → Authorized redirect URIs → + ADD URI. Allow a few minutes for the change to take effect.
Your app registration → Authentication → Web → Add URI.
Your application → General → Sign-in redirect URIs → Add URI.
Skip this and sign-in fails at the redirect with redirect_uri_mismatch (Google), AADSTS50011 (Entra), or invalid_redirect_uri (Okta) — after the sign-in screen appears, not before.
Get a client ID for the app
AIControls issues client IDs through dynamic client registration (RFC 7591). There is no console screen for this — register once with a single request and keep the client_id it returns:
curl -X POST https://YOUR_GATEWAY/oauth/register \
-H 'content-type: application/json' \
-d '{
"client_name": "claude-desktop",
"redirect_uris": ["http://127.0.0.1:8123/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}'
{"client_id": "oc_09fe6c9b6911e6a5d2c91709eb64bfc9", "token_endpoint_auth_method": "none", ...}
token_endpoint_auth_method: "none" is what makes it a public client, which is what the app needs. Registration is unauthenticated by design, per RFC 7591 — the client ID alone grants nothing without a completed sign-in.
Pick the loopback port yourself and register the exact callback URI; the app must then be told the same port. If you see a redirect_uri mismatch, re-register with the URI the app actually used.
Configure the app
| Setting | Value |
|---|---|
| Gateway base URL | https://YOUR_GATEWAY |
| Client ID | the oc_… value from above |
| Issuer URL | https://YOUR_GATEWAY |
| Bearer token | access token |
| Scopes | mcp |
| Redirect port | the port you registered (8123 above) |
Bearer token must be "access token", not the "ID token" default. On this path AIControls issues an access token and no ID token, so a client left in ID-token mode has nothing to send.
This is deliberate and will not change. An ID token is an authentication assertion audienced to a client, whereas AIControls is a resource server: accepting client-audienced tokens as API credentials invites audience confusion, carries no scope to enforce least privilege, and has no revocation story. The access token issued here is opaque and its revocation is checked on every request. See Tokens minted by AIControls.
Authorization and token endpoints are discovered automatically — AIControls publishes its metadata at both /.well-known/openid-configuration and /.well-known/oauth-authorization-server, so clients that expect an OpenID Provider and clients that expect an OAuth authorization server both work. If your client asks for those URLs explicitly, they are https://YOUR_GATEWAY/oauth/authorize and https://YOUR_GATEWAY/oauth/token.
What happens at sign-in
- The app fetches the discovery document and registers or reuses its client ID.
- It opens
/oauth/authorize. The page offers Sign in with SSO — which redirects to your IdP — whenever SSO / OIDC is configured with an Issuer URL, Client ID, and Redirect URL (or, rarely, a separate provider is configured under the advanced Admin SSO option). A password form is offered alongside it for local accounts. - Your IdP returns to AIControls, which completes the exchange with the client secret — the step a public client cannot perform.
- AIControls mints an access token and the app presents it as the bearer on every
/v1/messagescall.
Because AIControls issues the final token, its lifetime is governed by AIControls rather than your IdP, and no client secret is ever distributed to end-user machines.
Restrictions on the SSO / OIDC provider apply to this flow as well: a Google Workspace Hosted Domain, or a GitHub organization, is checked against the token's hd claim at step 3, before AIControls issues anything. An account your IdP will happily authenticate — a personal Google account, say — cannot complete sign-in here if it falls outside the domain you set.
Verify
curl -s https://YOUR_GATEWAY/.well-known/openid-configuration | jq '.issuer, .authorization_endpoint'
The issuer must equal your gateway's public URL. If it comes back as http://localhost:8080, set proxy.mcpIssuerURL to the public URL — clients follow the advertised endpoints, and localhost values make sign-in fail after discovery succeeds.
On Kubernetes, set the Helm value:
helm upgrade aicontrols ... --set proxy.mcpIssuerURL=https://YOUR_GATEWAY
When left empty, the chart derives the issuer from your Ingress or Gateway hostname, which is correct for most installs. Set it explicitly whenever clients reach the gateway through a hostname the chart doesn't know about — an external load balancer, a CDN, or custom DNS.
The issuer is the iss claim of every token AIControls mints, and the verifier matches on it. Changing it invalidates tokens already issued, so clients sign in again after the change.
Token refresh, and one Google caveat
Granting offline_access returns a refresh token, and the client refreshes the bearer silently before it expires. Google is the exception — it rejects offline_access as a scope, so a Google-backed client has no refresh token at all. When a refresh fails — the session was revoked, or the user is past the IdP's idle window — they re-authenticate in the browser.
Google Workspace, id_token mode only. Google never returns an id_token on a refresh-token grant, so a Google-backed gateway in the default id_token mode prompts a browser sign-in roughly once per ID-token lifetime (about an hour).
Use access_token mode to avoid it — that mode is unaffected on every provider. Microsoft Entra ID and Okta both return a fresh id_token on refresh and are unaffected either way.
Verify
Sign in from Claude Desktop, send one message, then check Security Events (All activity tab) in the AIControls console. The request should appear attributed to the signing-in user, with their groups populated.
If group-scoped budgets or model policies do not apply as expected, open the audit event and check the user's groups are present — an empty group list means the claim is missing from the token, not that the policy is wrong.
Troubleshooting
Claude Desktop displays the gateway's error message directly, and AIControls includes the token's own issuer and audience in it — enough to tell whether the app signed in against the tenant and application you expected.
The expected values are in the proxy logs, not the response (publishing them would let anyone enumerate your configuration). When the presented values look right but the token is still rejected, check the logs for the warning naming the mismatch.
| Symptom | Cause | Fix |
|---|---|---|
aud in the error is not one you configured | The app's client ID is not registered as an accepted audience | Add it under Additional Accepted Audiences (Step 3) |
| Token rejected although the issuer looks correct | Trailing slash mismatch — Auth0 and Entra ID v1 publish an issuer ending in / | Enter the issuer exactly as the provider publishes it |
| Startup log warns a provider "will reject all tokens" | That provider has no audience configured | Add an audience, or set skipAudienceCheck deliberately |
iss in the error does not match the configured issuer | Wrong tenant, or an Entra v1 access token | Check the tenant ID; set accessTokenAcceptedVersion: 2, or add a provider for the v1 issuer (Step 2) |
Token exchange failed (HTTP 401) | Registered as a confidential/Web client | Re-register as a public (native / mobile-and-desktop) client (Step 1) |
Error 400: invalid_scope, invalid=[offline_access] | Google rejects offline_access as a scope | Set the client's scopes to openid profile email |
client_secret is missing at the token exchange | Google, any client type — it requires a secret that a public PKCE client cannot send | Not configurable around; see Step 1 for the alternatives |
Error 401: invalid_client, "OAuth client was not found" | The Client ID is wrong or unsubstituted | Check the value against the console |
OIDC discovery failed (HTTP 401) | The Issuer URL names a host that serves no discovery document. Pointing it at the gateway is correct only on versions that publish /.well-known/openid-configuration | Verify with curl <issuer>/.well-known/openid-configuration; see Gateway as the authorization server |
Sign-in succeeds, then Gateway returned no usable models | No upstream is configured, so there was nothing to discover — an authentication success, not a failure | Add an upstream under Settings → Models, or list models in the client to skip discovery |
Model discovery — found 0 models, with an upstream configured | The upstream was queried and rejected the request — most often the provider API key. An empty list is not the same as a provider that answered | Use Test Connection on the upstream, then check the proxy log for model discovery: upstream rejected the model list request and the status it names |
model discovery failed for every configured upstream (HTTP 502) | Every upstream the gateway asked returned an error. The message names each one and its status | Fix the credential the status points at; 401 means the key was rejected, not that the model list is empty |
No usable models and the token was never checked | GET /v1/models needs no authentication, so reaching it proves the sign-in flow completed, not that the token was accepted | Send one message: 402 (license gate) or 200 both mean the token was accepted; 401 means it was not |
AADSTS50011 — redirect URI mismatch | The /callback path is missing; Entra wildcards the port but not the path | Register http://127.0.0.1/callback exactly (Entra). Okta: register the exact port you pinned with redirectPort |
Redirect mismatch naming …/oauth/sso/callback, on the gateway-as-AS path | The gateway's own callback is not registered on the IdP client — it is a second URI, not a replacement for the one in Redirect URL | Add https://YOUR_GATEWAY/oauth/sso/callback to that client, using the gateway's URL and not the console's (details) |
| Redirect mismatch naming the console's host or port | The registered URI was built from the admin console's address; the gateway derives this callback from its own public URL | Register the URI under the gateway's URL — the Proxy URL value on the SSO / OIDC tab |
/oauth/authorize shows only a password form, no Sign in with SSO | No provider is usable for it: SSO / OIDC is either unset or missing a Redirect URL, and no separate provider is configured under the advanced Admin SSO option either. A provider whose discovery endpoint is unreachable also yields no button | Fill in SSO / OIDC's Issuer URL, Client ID, and Redirect URL; then check the proxy logs for an OIDC discovery warning naming the issuer |
| Browser sign-in prompt roughly every hour | Google Workspace in id_token mode — Google returns no id_token on refresh | Switch the client to access_token mode (details) |
AADSTS65001 in access_token mode | The gateway app does not Expose an API, or the client lacks that delegated permission | Do both, and set the client's scopes to that API scope |
| Requests rejected for carrying no usable principal | Token has neither an email nor the configured user identifier claim | Set User ID Claim to a claim the token actually contains — oid on Entra (Step 4) |
| Intermittent expiry rejections | Clock drift between your IdP and the proxy | Raise Clock Skew Tolerance (default 60 seconds) |
| Team model limits not applying to a user | No portal record, so the global ceiling is used | Enable Auto-Provision Users (Step 5) |
503 with No API key configured on upstream … | Routing sent the request to a provider, or a route's first member, that has no API key. The signed-in user's token is not a provider credential, so there is nothing to forward. The message names the provider, the route, the model, and the identity source (oidc for SSO sign-in, pat for an access token) | Add an organization key to that provider, or add a provider with a key to the route; see Credential mode |
Model discovery
Claude Desktop discovers the models your gateway offers from GET /v1/models. Alongside the standard fields, AIControls reports anthropic_family_tier (opus / sonnet / haiku) and supports1m for Anthropic models, so the app can tell which Claude tier an opaque model ID maps to and which models have the 1M-token context window. This works for gateway aliases and for Bedrock/Vertex model IDs. No configuration needed — see Model discovery fields.
Which models the app offers depends on how the upstream is configured. With Live Model Discovery enabled, the gateway asks the provider directly and returns only what that provider currently serves. With it disabled and no explicit model list, the gateway falls back to its pricing catalogue, which retains retired model IDs so historical spend can still be costed — so the picker may offer models the provider no longer accepts. Enable Live Model Discovery, or set an explicit model list on the upstream, to pin the menu.
The list is not filtered by your model allowlists. Allowlists are enforced when a request is made, so a model can appear in the picker and still be denied on use.
How Cowork sessions are attributed
Claude Desktop runs Cowork sessions by embedding the Claude Code binary, so requests from a Cowork task and from Claude Code in a terminal come from the same program. The audit log distinguishes them: Cowork sessions appear as Claude for Work, terminal Claude Code as Claude Code, and requests from the desktop app itself — generating a session title, for example — as Claude Desktop. Expect one Cowork session to produce more than one client label, since the app and its embedded agent both make calls.
Configuration reference
Self-managed deployments can configure providers in YAML instead of the console. Every provider must specify an audience:
identity:
oidcProviders:
- name: entra-gateway
issuer: https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0
# Accept both the portal's client ID and Claude Desktop's.
audiences:
- YOUR_PORTAL_CLIENT_ID
- YOUR_CLAUDE_DESKTOP_CLIENT_ID
userIDClaim: oid # Entra: `sub` is pairwise per app registration
groupsClaim: groups
clockSkewSeconds: 60
autoProvision: true # only needed for team-scoped model access
autoProvisionFallback: none # groups that map to no team get no team,
# not the (possibly permissive) default one
usage: [llm] # accept these tokens at the LLM gateway only
See the Identity provider fields reference for every available field.
Entra ID emits group GUIDs, not display names. If your team group bindings are written against names they will match no user, and everyone auto-provisioned lands on the autoProvisionFallback path. With the default setting that means the default team — which, if it is permissive, grants more model access than the user's real team would, silently. Bind against the GUIDs Entra actually sends, and see When no group binding matches.
Related
- Claude Code — connecting the CLI, which uses access tokens
- Admin Console SSO — enable admin console sign-in through this same provider (or, rarely, a separate one)
- Identity provider fields — full field reference and claim mapping