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)

None

Canonical flow

  1. User signs in to client app (front-end or BFF).
  2. App gets user access_token with broad or app-specific audience.
  3. App needs to call downstream service.
  4. App performs token exchange to request token for target service audience.
  5. 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

  • iss is your expected realm issuer.
  • aud includes only intended downstream service.
  • azp equals requester client.
  • exp is short enough for delegated use.
  • scope is 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

  1. Confirm target client id exists (iam-dev).
  2. Add audience mapper (usually in client scope).
  3. Attach scope to requester (default or optional, then request with scope=).
  4. Configure token exchange permissions between requester and target.
  5. 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)

None

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)

None

Required data model

  • impersonation_id
  • actor_user_id (real operator)
  • effective_user_id (target student)
  • reason
  • started_at
  • ended_at
  • status (active / ended)
  • source_ip
  • user_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

  1. Validate token/session.
  2. Detect impersonation mode.
  3. Enforce endpoint policy for effective user.
  4. Enforce additional policy for actor in impersonation mode.
  5. 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:

  1. Create confidential client (e.g. iam-admin-api).
  2. Enable service account.
  3. Keep credentials server-side only.
  4. Restrict network path to admin endpoints if possible.
  5. Rotate secrets periodically and on exposure.
  6. Assign minimal realm-management roles:
  • impersonation
  • view-users
  • query-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_started
  • impersonation_stopped
  • impersonation_action_performed
  • impersonation_denied

Recommended event payload fields:

  • event_id
  • event_name
  • actor_user_id
  • effective_user_id
  • impersonation_id
  • endpoint
  • http_method
  • decision (allow / deny)
  • reason
  • timestamp

Audit event lifecycle

None

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.