Enable token exchange
Allow your backend client to exchange a signed-in user’s token for a Chat user token (Token exchange, RFC 8693). The user signs in once. Your web or mobile app then gets its Chat token from a route of your backend, with no second login and no second OAuth client.
Prerequisites
Section titled “Prerequisites”- A backend client: CONFIDENTIAL, on a tenant application.
- Your users sign in to your application through the Identity Server and your app has a token for your own API.
- Your web app needs nothing for Chat on its own client: no
chat:accessgrant and no extra redirect URI.
-
Check that token exchange is on. Token exchange is off by default. An Identity Server operator turns it on with the
TOKEN_EXCHANGE_ENABLEDsetting of the Identity Server. It is a global setting, not one you change per tenant. When it is on,/.well-known/openid-configurationlistsurn:ietf:params:oauth:grant-type:token-exchangeingrant_types_supported. -
Enable the grant type on the client. In the admin UI, open Clients, pick your tenant application, then your backend client. Choose Edit, tick token-exchange (RFC 8693) under Grant Types and save.
-
Grant the exchange permission. Open the client’s Permissions, then Token exchange. Pick the Chat API as the target resource, tick
chat:accessand save.
A tenant owner, or an admin with the resources.manage permission, can do step 3 for their own clients. No platform administrator is needed.
A scope that requires explicit user consent cannot be exchanged, because exchange has no consent screen. The Chat scopes are set up for exchange.
Verify
Section titled “Verify”Exchange a real user token for your own API. $USER_TOKEN is the signed-in user’s access token for your API (not an ID token).
curl -s -X POST "$CCS_ISSUER_URL/oauth/token" \ -u "$CCS_CHAT_SYNC_CLIENT_ID:$CCS_CHAT_SYNC_CLIENT_SECRET" \ --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \ --data-urlencode "subject_token=$USER_TOKEN" \ --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \ --data-urlencode "resource=$CHAT_API_RESOURCE" \ --data-urlencode "scope=chat:access"{ "access_token": "eyJ...", "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", "token_type": "Bearer", "expires_in": 600, "scope": "chat:access"}Your backend does this with exchangeToken(). See Issue Chat tokens to your users.
The exchanged token lives at most 10 minutes and never longer than the user’s own token. There is no refresh token: exchange again when it runs out. Decode it and check sub (the user), aud (chat-api) and scope (chat:access).
Troubleshooting
Section titled “Troubleshooting”| Error | Check |
|---|---|
unsupported_grant_type |
Token exchange is off on the Identity Server. Ask the operator to turn it on. |
unauthorized_client |
The client is CONFIDENTIAL, belongs to a tenant application, has the grant type, and has a Token exchange entry for the Chat API. |
invalid_target |
resource is the identifier, not the audience, and Chat is not switched off for your application. |
invalid_scope |
The scope is in the client’s Token exchange list, not only its Resource Permissions. It is not chat:sync. |
invalid_grant |
The subject token is your own API’s access token for a client of the same application, not expired, and the user has not signed out. It is not an ID token. |
access_denied |
The user is active, a member of the tenant and has access to the application. |
| HTTP 429 | Too many exchanges. Wait for Retry-After seconds and cache the token on the app side. |
The Identity Server documentation lists every rule and rate limit: token exchange.