Session Transfer (native to web)
Native applications (for example mobile apps) often open company-owned web applications in the system browser: centralized profiles, webshops, help portals, and similar. Those sites typically expect an active IdP session in the browser—the same kind of session that powers Single Sign-On (SSO) for pure web apps—not only a token inside the native app.
This guide describes how session transfer bridges from a native-authenticated user to a browser session and web client tokens without putting long-lived tokens in URLs.
Overview
Session transfer is for the case where the user is authenticated in a native client but the system browser does not yet have an IdP SSO cookie. You obtain a short-lived Session Transfer Token (STT) from the token endpoint (~10 seconds), then start a normal authorization code request in the browser with the stt parameter so the web client receives codes and tokens and the browser can establish a session.
Session transfer requires the tenant feature tag stt. Without it, minting an STT and redeeming stt on authorize are not available.
Prerequisites
Configure both clients in app settings (for example via Create app or Update app on PUT /apps-srv/clients):
- Tenant — Feature tag
sttenabled. - Native app — Include
session_transferingrant_types. That native client is the one that callsPOST /token-srv/tokenwithgrant_type=session_transfer. - Web app (
target_client_id) — List the native app’sclient_idundernative_to_web_sso_settings.allowed_native_clients(Trustdesk: native-to-web SSO / allowed native clients). The web client only accepts session transfer from allowlisted native clients.
session_transfer is a public grant type: the native client can mint an STT with client_id plus a valid subject_token. Confidential-client authentication is optional for this grant unless your deployment policy requires it.
When you need it — and when you don’t
Sign-in ran in an in-app WebView. Cookie isolation means the system browser has no shared SSO cookie when you later open Safari or Chrome.
The native app still refreshes access tokens, but the browser IdP session cookie has already expired. Opening a web URL hits no active SSO.
Login happened via device code (or similar) without a normal hosted-pages redirect on this device. The system browser has no prior cidaas SSO cookie.
If the system browser already has a valid cidaas session, session transfer is not required — a normal authorization request is enough.
Sequence diagram
Integrator-facing HTTP calls only (internal IdP steps omitted):
What not to do: pass tokens in the URL
Appending access or refresh tokens to a web URL query string is insecure and incomplete:
- Security risk — Tokens can appear in browser history, referrer headers, and server logs.
- Wrong audience — The web app needs tokens for its own
client_idand scopes. - No IdP session — A query token does not create a cidaas SSO cookie for other web apps.
- Poor lifecycle — The web app cannot refresh/renew the way it does after a proper OAuth code flow.
How session transfer works
subject_token at POST /token-srv/token (grant_type=session_transfer) and sets target_client_id.sttGET /authz-srv/authz for the web client_id, including stt and (recommended) PKCE.The STT is returned as access_token in the mint response. It is an opaque identifier (not a JWT) with expires_in: 10. Pass that value as the stt query parameter on authorize.
Use the web client as client_id on authorize (the same client you intended as target_client_id when minting). Access control is enforced via the web app’s allowed native clients allowlist.
Security controls
AUTH10085.When the STT is minted, cidaas stores the client IP of that request. The authorization request with stt must use the same IP. VPN connect/disconnect, network switches, or proxy changes between mint and authorize cause AUTH10085. Mint the STT and open the authz URL as close together as possible on the same device/network. On AUTH10085, obtain a new STT from the current IP.
Authentication context (acr, amr, auth_time)
When minting an STT from an active native session, cidaas preserves authentication context:
acr— Assurance level from the native authentication is carried into the transferred session.amr— Native authentication methods are retained, and the session-transfer marker"100"is appended (for example["touchid","100"]).auth_time— Original native authentication time is preserved (not reset at transfer).
That supports step-up checks (acr_values) and max_age against the real login time across native and web.
Existing browser session
| Browser cookie | Behavior |
|---|---|
| Same user as STT | Authorization can complete using the existing session. The STT is still consumed. |
| Different user | The user is sent to login (no account-switch prompt). The STT is already consumed, so the transfer cannot be retried with the same token. |
End-to-end examples
Replace placeholders with your tenant base URL, client IDs, and tokens.
Step 1 — Issue an STT: POST /token-srv/token
JSON and application/x-www-form-urlencoded bodies are both accepted. The access_token field in the response is the opaque STT.
- Access token as subject_token
- Refresh token as subject_token
POST https://{base-url}/token-srv/token
Content-Type: application/json
{
"grant_type": "session_transfer",
"client_id": "{native_client_id}",
"subject_token": "{current_access_token}",
"subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
"target_client_id": "{web_client_id}"
}
POST https://{base-url}/token-srv/token
Content-Type: application/json
{
"grant_type": "session_transfer",
"client_id": "{native_client_id}",
"subject_token": "{current_refresh_token}",
"subject_token_type": "urn:ietf:params:oauth:token-type:refresh_token",
"target_client_id": "{web_client_id}"
}
Example token response (STT):
{
"access_token": "{stt_value}",
"expires_in": 10
}
expires_in is 10. Open the authorization URL in the system browser immediately after minting, on the same network/IP.
Step 2 — Authorize in the browser: GET /authz-srv/authz
Open the system browser (or a browser context that shares cookies with it) for the web client. Include stt. Include PKCE (code_challenge, code_challenge_method=S256) whenever the web app requires PKCE, and for public clients in general.
Example URL (one line; URL-encode every query value in production):
GET https://{base-url}/authz-srv/authz?response_type=code&client_id={web_client_id}&redirect_uri={url_encoded_redirect_uri}&scope=openid%20profile%20email&state={opaque_csrf_token}&nonce={nonce_if_using_openid}&code_challenge={pkce_code_challenge}&code_challenge_method=S256&stt={stt_value_from_step_1}
cidaas redirects to redirect_uri with ?code=...&state=... (and may set SSO cookies).
Step 3 — Exchange the code: POST /token-srv/token
POST https://{base-url}/token-srv/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"client_id": "{web_client_id}",
"client_secret": "{web_client_secret_if_confidential}",
"code": "{authorization_code_from_redirect}",
"redirect_uri": "{same_redirect_uri_as_in_step_2}",
"code_verifier": "{pkce_code_verifier}"
}
Full parameter lists: Token endpoint and Authorization request. For PKCE see PKCE. For the code flow see Authorization Code Flow.
API reference
| Step | Operation | Endpoint | Role in session transfer | Documentation |
|---|---|---|---|---|
| 1 | Issue STT | POST /token-srv/token | grant_type=session_transfer with subject_token, subject_token_type, target_client_id; response access_token is the opaque STT (expires_in: 10) | Token endpoint |
| 2 | Authorize | GET /authz-srv/authz | Web client_id, stt, standard OAuth params; PKCE when the web app requires it | Authorization request |
| 3 | Exchange code | POST /token-srv/token | grant_type=authorization_code with code, redirect_uri, code_verifier (if PKCE) | Token endpoint |
| — | Discovery (optional) | GET /.well-known/openid-configuration | Read authorization_endpoint, token_endpoint, and other metadata | OpenID configuration |
Validate issued JWTs with Validate Token.
Related topics
Please contact us directly on our support page or reach out to cidaas support at [email protected].