A short story from production-like troubleshooting
We started with what looked like a simple requirement: "Support operators need to log in as students to resolve account issues quickly." Token Exchange worked immediately for cross-service access, so it was tempting to use it for impersonation too.
Then reality hit:
- audience mapping errors,
- unsupported
requested_subject, - successful impersonation API response but browser still redirected to login.
The root cause was not implementation quality. It was conceptual mismatch. We were trying to use a delegated token mechanism to solve an identity-switching problem.
If you build internal systems (education, finance, healthcare, enterprise), this requirement eventually appears:
Operator should be able to log in as a student/user to troubleshoot issues.
Most teams initially assume OAuth Token Exchange is enough. Then they hit confusing behavior: audience errors, unsupported parameters, redirect loops, and session mismatch.
This article is a practical deep-dive from implementation to production architecture.
1) Problem Framing: Two Similar Terms, Two Different Protocol Goals
At a glance, Token Exchange and Impersonation look similar because both involve one actor obtaining access for another context. But they solve different identity problems.
- Token Exchange (RFC 8693): exchange token A for token B with adjusted audience/scope/token type.
- Impersonation: explicitly act as another principal (identity switch with strong audit requirements).
If your requirement is "operator becomes student", this is identity switching, so treat it as impersonation.
2) Token Exchange in Practice
Token Exchange is ideal for service delegation and least-privilege API access.
Delegation sequence (Token Exchange)

Canonical flow
- User signs in to client app (front-end or BFF).
- App gets user
access_tokenwith broad or app-specific audience. - App needs to call downstream service.
- App performs token exchange to request token for target service audience.
- Downstream service validates the exchanged token and authorizes action.
Example cURL
curl --location 'https://<host>/realms/<realm>/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
--data-urlencode 'client_id=<requester-client-id>' \
--data-urlencode 'client_secret=<requester-client-secret>' \
--data-urlencode 'subject_token=<access-token>' \
--data-urlencode 'subject_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'requested_token_type=urn:ietf:params:oauth:token-type:access_token' \
--data-urlencode 'audience=<target-client-id>' \
--data-urlencode 'scope=openid profile email'What to verify in exchanged token
issis your expected realm issuer.audincludes only intended downstream service.azpequals requester client.expis short enough for delegated use.scopeis minimal and action-appropriate.
3) Why Requested audience not available Happens
Common error:
{
"error": "invalid_request",
"error_description": "Requested audience not available: iam-dev"
}This means Keycloak cannot legally issue that audience for this requester under current config.
Root causes
- Target client does not exist or wrong client_id.
- Requester client not authorized to request target audience.
- Audience mapper not present in attached client scopes.
- Exchange permissions not granted (depending on Keycloak mode/version).
Fix path
- Confirm target client id exists (
iam-dev). - Add audience mapper (usually in client scope).
- Attach scope to requester (default or optional, then request with
scope=). - Configure token exchange permissions between requester and target.
- Re-test and inspect JWT payload.
4) Why requested_subject Fails in Standard Token Exchange
Typical response:
{
"error": "invalid_request",
"error_description": "Parameter 'requested_subject' is not supported for standard token exchange"
}This is expected for Standard Token Exchange in Keycloak.
Interpretation
- Standard exchange is not "become arbitrary user".
- It preserves principal context and issues a transformed token.
- Identity switch flows need impersonation controls, not token exchange parameters.
5) Impersonation in Keycloak: Operational Reality
Keycloak impersonation endpoint commonly returns:
{
"redirect": "https://<keycloak>/realms/<realm>/account",
"sameRealm": false
}This is usually success: Keycloak instructs browser/session transition.
Why local demos often "fail" with login page
In local setups:
- app runs on
localhost, - Keycloak runs on different domain,
- session cookies are domain-scoped.
So you may successfully call impersonation API but still land on login page because browser has no valid session for Keycloak domain.
Browser/session boundary (why redirect != active session)

6) Architecture Decision: Three Viable Models
Model A: Pure Keycloak Admin Impersonation
- Good for internal admin console usage.
- Browser/session behavior tightly coupled to Keycloak domain and login state.
- Harder to tailor business policy and per-action auditing in app domain.
Model B: App-Controlled Impersonation Session (Recommended)
- Your backend becomes policy enforcement point.
- Backend stores actor/effective identity context.
- Frontend receives app-native session/token with impersonation metadata.
- Better for enterprise auditing and workflow controls.
Model C: Hybrid
- Keycloak for base auth and admin constraints.
- App layer adds strict policy + audit + sensitive action gating.
7) Recommended Flow (Production)

Required data model
impersonation_idactor_user_id(real operator)effective_user_id(target student)reasonstarted_atended_atstatus(active/ended)source_ipuser_agent
Do not store full user entities in audit tables. Store IDs and resolve details via source services when needed.
8) Authorization Design: Role, Permission, and Scope
A safe design separates "who may impersonate" from "what can be done while impersonating".
Suggested controls
- Permission to start impersonation:
can_impersonate_student - Permission to stop impersonation:
can_end_impersonation - Optional permission for sensitive routes while impersonating:
can_impersonated_password_reset(default deny)can_impersonated_financial_update(default deny)
Runtime checks per request
- Validate token/session.
- Detect impersonation mode.
- Enforce endpoint policy for effective user.
- Enforce additional policy for actor in impersonation mode.
- Emit audit event with both identities.
9) Threat Model and Mitigations
Threat 1: Privilege escalation
- Risk: operator impersonates outside permitted scope.
- Mitigation: target constraints (faculty/program/unit filters), policy engine checks.
Threat 2: Silent misuse
- Risk: no visibility of impersonated actions.
- Mitigation: immutable audit stream and mandatory reason.
Threat 3: Session confusion
- Risk: UI hides impersonation state.
- Mitigation: persistent banner + dedicated stop action.
Threat 4: Secret leakage
- Risk: client_secret or admin token exposed in logs/chats.
- Mitigation: immediate secret rotation, redaction middleware, short token lifetime.
Threat 5: Replay
- Risk: stolen impersonation token reused.
- Mitigation: short TTL, device binding signals, anomaly detection.
10) Keycloak Configuration Checklist (Admin API Integration)
For backend-triggered admin operations:
- Create confidential client (e.g.
iam-admin-api). - Enable service account.
- Keep credentials server-side only.
- Restrict network path to admin endpoints if possible.
- Rotate secrets periodically and on exposure.
- Assign minimal realm-management roles:
impersonationview-usersquery-users
11) API Contract Example (snake_case)
Start impersonation
POST /api/impersonations/start
{
"target_user_id": "36887dac-91de-4d2a-b251-18f5f571ccb9",
"reason": "helpdesk_krs_issue"
}Response:
{
"impersonation_id": "imp_01jxyz",
"actor_user_id": "op_1001",
"effective_user_id": "std_21795",
"status": "active",
"expires_in": 900
}Stop impersonation
POST /api/impersonations/stop
{
"impersonation_id": "imp_01jxyz"
}Response:
{
"impersonation_id": "imp_01jxyz",
"status": "ended",
"ended_at": "2026-04-25T15:32:00Z"
}12) Observability and Audit Events
Recommended event names:
impersonation_startedimpersonation_stoppedimpersonation_action_performedimpersonation_denied
Recommended event payload fields:
event_idevent_nameactor_user_ideffective_user_idimpersonation_idendpointhttp_methoddecision(allow/deny)reasontimestamp
Audit event lifecycle

13) Troubleshooting Matrix
14) Decision Table
15) Final Takeaway
Use the protocol for what it is designed to solve:
- Token Exchange is for delegated access and audience/scopes transformation.
- Impersonation is for controlled identity switching with heavy governance.
When teams separate these concerns clearly, they get:
- safer authorization boundaries,
- better incident traceability,
- and fewer brittle authentication edge cases.
Appendix A: Practical cURL Snippets
Get service-account token (admin client)
curl --location 'https://<host>/realms/<realm>/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=<admin-client-id>' \
--data-urlencode 'client_secret=<admin-client-secret>'Search user
curl --location 'https://<host>/admin/realms/<realm>/users?username=<username>&exact=true' \
--header 'Authorization: Bearer <admin_access_token>'Trigger Keycloak impersonation endpoint
curl --location --request POST 'https://<host>/admin/realms/<realm>/users/<target_user_id>/impersonation' \
--header 'Authorization: Bearer <admin_access_token>'Appendix B: Pre-Production Readiness Checklist
- Permission model reviewed by security team.
- Mandatory reason enforced server-side.
- Actor/effective identity captured in every audit event.
- UI warning banner implemented and tested.
- Max impersonation TTL configured.
- Sensitive actions blocked or re-approved during impersonation.
- Secret rotation runbook documented.
- Incident response query/playbook validated.
Closing: What to tell your team
If your team is debating whether Token Exchange can "be enough" for operator-as-user features, use this rule:
- If you only need delegated API access, use Token Exchange.
- If you need to become another principal, design explicit impersonation controls.
This one decision prevents most of the painful edge cases in identity projects.