Skip to content

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.

  • 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:access grant and no extra redirect URI.
  1. Check that token exchange is on. Token exchange is off by default. An Identity Server operator turns it on with the TOKEN_EXCHANGE_ENABLED setting of the Identity Server. It is a global setting, not one you change per tenant. When it is on, /.well-known/openid-configuration lists urn:ietf:params:oauth:grant-type:token-exchange in grant_types_supported.

  2. 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.

  3. Grant the exchange permission. Open the client’s Permissions, then Token exchange. Pick the Chat API as the target resource, tick chat:access and 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.

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).

Terminal window
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"
}

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).

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.