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.