A connector (MCP server) can call your API as the user signed in on the site hosting the chatbot: the API sees that user (sub), with their rights and context, as if they had made the calls themselves. They never go through Squadico. This guide is for whoever runs your identity provider and whoever embeds the chatbot.
How it works
Before each message, the chatbot asks your page for the signed-in user's token (getSubjectToken) and sends it to Squadico.
Squadico exchanges that token at your authorization server for a new one: same user, meant for the connector alone. This is the OAuth 2.0 Token Exchange standard (RFC 8693), or the on-behalf-of flow (RFC 7523) for Microsoft Entra ID.
Squadico calls the connector with the new token. Your site's token is never sent to the connector, nor kept.
The connector checks the token and calls your API as the user, ideally through its own token exchange.
What you need
An OAuth authorization server that can exchange tokens (RFC 8693) or do on-behalf-of.
A Squadico application with identity verification on: the token is only accepted for a visitor whose identity your server signed (digest).
An MCP connector that accepts tokens issued by that authorization server.
1. In your identity provider
Three things, whatever the provider: a confidential client for Squadico allowed to exchange tokens; a recipient (audience) standing for the connector; and, with some providers, the Squadico client in the audience of your site's tokens.
Keycloak 26.2 and later
Clients → Create client: "squadico", Client authentication on, Standard flow on, and tick "Standard Token Exchange". Valid redirect URIs: the Squadico callback URL (see step 2). Copy the secret (Credentials tab).
Create a client for the connector, for instance "route-planner" (Client authentication on). Its id is the audience asked for.
"squadico" client → Client scopes → squadico-dedicated → Add mapper → Audience: Included Client Audience = route-planner, Add to access token on. Without it, Keycloak refuses to produce that audience.
Your site's front client → Client scopes → …-dedicated → Add mapper → Audience: Included Client Audience = squadico. Keycloak only exchanges tokens whose audience includes the requester.
Keycloak 23 to 26.1
Start Keycloak with --features=token-exchange,admin-fine-grained-authz (token exchange is a preview feature there, known as V1).
Create the "squadico" client (confidential, Standard flow, callback URL) and the connector's client, as for Keycloak 26.
Connector's client → Permissions → turn permissions on, open "token-exchange" and attach a Client policy allowing "squadico".
In Squadico, always set the audience (the connector's client id) — V1 does not add it otherwise — and put the Squadico client id in "Audience required of the token handed over": V1 exchanges any token of the realm, so Squadico checks it itself.
Okta
Create an API Services or Web application for Squadico, with a client secret, and allow the "Token Exchange" grant in its settings.
In your authorization server (Security → API), add a scope for the connector and an access rule allowing the Squadico application to use the Token Exchange grant.
In Squadico: RFC 8693, scope = the scope you created (Okta requires one), audience = your authorization server's audience.
Microsoft Entra ID
Register an application for the connector and expose an API (Expose an API → Application ID URI, for instance api://route-planner).
Register an application for Squadico with a client secret, and grant it the delegated permission on the connector's API (API permissions), with admin consent.
Your site's tokens must be issued for the Squadico application (audience = its Application ID URI): that is what on-behalf-of requires.
In Squadico: "On-behalf-of (RFC 7523)", scope = api://route-planner/.default.
Auth0
Auth0 offers token exchange through "Custom Token Exchange": create an exchange profile and the action validating the token received, per Auth0's documentation.
Create a machine-to-machine application for Squadico, authorized on the connector's API.
In Squadico: RFC 8693, audience = the connector's API identifier, and the token type your exchange profile expects.
Other servers (Ping, Curity, ForgeRock, Zitadel…)
Create a confidential client for Squadico and allow it the urn:ietf:params:oauth:grant-type:token-exchange grant.
Define the connector's recipient (audience or resource) and allow Squadico to ask for it.
In Squadico, match the exchange settings to what your server expects: audience, scope, sending resource, client authentication method.
2. In Squadico
Settings → your organization → Tools → the connector → Configure OAuth: your authorization server's authorization and token URLs (if they were not discovered), the Squadico client's id and secret.
Add the Squadico callback URL to the client's redirect URIs: the organization's authorization uses it.
Authorize the connector once as the organization: that token only reads the tool list, never calls a tool.
Identity used for calls → "The user of the site hosting the chatbot", then set up the exchange (see the table).
Exchange settings
Setting
What it does
Default
Exchange type
RFC 8693 for most servers; on-behalf-of (RFC 7523) for Entra ID.
RFC 8693
Type of the token handed over
What your site sends: access token, JWT or ID token. Keycloak only accepts access tokens.
Access token
Audience
The recipient asked for the new token.
None
Scope
Required by some servers (Okta, Entra ID).
None
Send resource
Sends the connector's URL (RFC 8707), as the MCP specification recommends. Turn off if your server refuses the parameter.
Yes
Client authentication
HTTP Basic, the method every OAuth server must accept, or the secret in the request body.
HTTP Basic
Audience required of the token handed over
When set, Squadico refuses any token that does not carry it, before even asking for the exchange.
None
3. On your site
Turn on the application's identity verification, compute the user's digest on your server, and give the chatbot a function returning the current token. It is called before every message: return a fresh token, or null when nobody is signed in.
// On your server, when the page is rendered — never in the browser
const digest = createHmac('sha256', process.env.SQUADICO_IDENTITY_SECRET)
.update(user.id)
.digest('hex')
// In the page
const chatbot = document.querySelector('squadico-chatbot')
chatbot.user = { id: user.id, name: user.name, digest }
// Called before every message: return the current token, refreshed if needed
chatbot.getSubjectToken = () => auth.getAccessToken()
4. On your MCP connector
Check the token's signature (your authorization server's public keys), its issuer (iss) and that its audience (aud) names the connector.
sub is the user; azp (or appid) names Squadico, which helps trace or restrict what an agent does.
To call your API, make your own token exchange towards the API's audience rather than forwarding the token received: the MCP specification advises against forwarding a token as is.
Testing
Get a token for a user of your site, then make the exchange Squadico will make. The token you get must carry the same sub and the connector's audience. You can then paste the original token in the "Test user token" field of your application's sandbox.
TOKEN_URL=https://<your-idp>/…/token
# The exchange Squadico makes (RFC 8693, HTTP Basic client authentication)
curl -s "$TOKEN_URL" \
-u "<squadico-client-id>:<squadico-client-secret>" \
-d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
-d subject_token="$USER_TOKEN" \
-d subject_token_type=urn:ietf:params:oauth:token-type:access_token \
-d audience=<connector-audience> \
-d resource=<connector-url>
# Expect a token with the same "sub" and the connector in "aud"
Troubleshooting
Symptom
Likely cause
The agent says no session was handed over
The site sends no token (getSubjectToken), or identity verification is off on the application.
Refusal "subject_token_needs_verified_identity"
A token was sent for a visitor whose identity is not verified: add the digest to user.
The agent asks the user to sign in again
The authorization server refused the exchange: expired token, client not allowed to exchange, audience not allowed, or required audience missing from the token.
403 or invalid_client on the exchange
A public Squadico client instead of a confidential one, a wrong secret, or a client authentication method the server does not accept (try the other).
invalid_target or unsupported parameter
The server refuses resource or audience: untick "Send resource" or clear the audience.