No. By default, ordinary support staff shouldn’t be able to impersonate an admin, an owner or another staff member. Impersonation gives the agent the target’s powers, so acting as an admin means holding an admin’s access to roles, billing and security settings. Good impersonation permissions answer two questions separately: who may impersonate, and who may be impersonated by whom.
Most tools make the first question easy and leave the second to you. This guide covers how to define a permission hierarchy for support access, what common auth providers and libraries enforce, how to stop chained impersonation, and how to test it.
Why is impersonating an admin different?
Impersonating a user doesn’t add new permissions to the session. It borrows the target’s permissions. That’s fine for an ordinary user with ordinary rights. For a privileged user, it’s privilege escalation with a support ticket attached.
“Admin” means two different things here, and both need rules:
- Your platform’s own staff and admins. These accounts can reach internal tools, other tenants or configuration. Impersonating them through a support tool should be impossible.
- A customer’s organization admins and owners. They can invite users, change roles, rotate API keys and manage billing for their organization. Support sometimes does need to see what they see, but not with the power to act as them freely.
Treat both as privileged targets: accounts that need a stricter rule than an ordinary user.
How should you define impersonation permissions?
Write the policy as a pair: this kind of actor may open this kind of target, with this scope and this approval. A permission on the actor alone (“support can impersonate”) silently means “support can impersonate anyone”.
A starting hierarchy might look like this:
| Target | Ordinary support | Senior support or on-call engineer | Notes |
|---|---|---|---|
| Ordinary customer user | Read-only, one approval | Read or write, one approval | Still subject to the customer’s own permissions |
| Customer admin or owner | Denied | Read-only, second approver, shortest duration | Keep sensitive actions blocked |
| Your own staff account | Denied | Denied | Not through support tooling at all |
| Platform admin or superuser | Denied | Denied | Use a separate, audited process |
| User in an unassigned customer | Denied | Per your assignment policy | See assigned customer accounts |
Resolve both sides from trusted server-side data: the staff identity from verified authentication, and the target’s role from your database at the moment the credential is issued. A role shown in a search result may be stale by the time access is approved.
How do auth providers and libraries handle admin targets?
Some tools check the target. Many only check the actor.
- Better Auth. Admins can’t impersonate other admin users (opens in a new tab) unless their role has the
impersonate-adminspermission, which is separate fromimpersonate. That check covers users the plugin treats as admins. If your customer-admin role lives somewhere else, such as organization membership, add your own rule. See Better Auth impersonation for support. - Keycloak. Any user with the realm’s impersonation role can impersonate users (opens in a new tab). Fine-grained admin permissions can block impersonation of specific users (opens in a new tab), but they only grant extra permissions and can’t override the built-in admin roles. Give support scoped permissions rather than the realm-wide role.
- django-hijack. The default lets superusers hijack any active user. The built-in
superusers_and_staffcheck (opens in a new tab) lets staff hijack users who are neither staff nor superusers, which blocks staff-on-staff by design. - laravel-impersonate. Everyone can impersonate and be impersonated (opens in a new tab) until you override
canImpersonate()andcanBeImpersonated(). The built-in controller checks them separately (opens in a new tab), one on the actor and one on the target. A rule that depends on the pair, such as an agent’s assignment, needs your own controller. - Symfony. A custom voter for
switch_user(opens in a new tab) receives the target user, so it can decide per pair. - WorkOS. Once enabled, Dashboard impersonation (opens in a new tab) lets any member of your WorkOS team impersonate any of your users. The docs don’t describe a per-target rule, so control who is on your WorkOS team.
- Auth0. For Session Delegation (opens in a new tab), a Custom Token Exchange Action authorizes each delegation. Put your target rules in that Action.
The pattern is the same everywhere: a target check either exists in the tool and covers the roles you care about, or it’s code you write.
Should impersonation be allowed to chain?
No. Chained impersonation means starting a new impersonation from a session that’s already impersonating someone. A support agent acting as a customer admin who could impersonate others would end up acting as a third user, with the audit trail pointing at the wrong person.
Tools handle this differently:
- Auth0 Session Transfer Tokens accept only one level of actor (opens in a new tab). Nested actors are rejected, even though Custom Token Exchange access tokens allow up to five levels.
- laravel-impersonate’s built-in controller returns 403 (opens in a new tab) if you’re already impersonating.
- Symfony exits the current switch (opens in a new tab) before switching to another user, then checks permission against the original user.
- django-hijack keeps a stack of previous users (opens in a new tab) and checks permission against whoever is currently logged in. So a superuser who hijacks another superuser could hijack again.
Make your rule explicit. Refuse to issue an impersonation credential to any session that already carries an actor, and always attribute actions to the original human.
When do you need approval tiers?
Use tiers when the risk of the target or the scope differs. A read-only look at an ordinary user’s dashboard shouldn’t need the same process as write access to a customer owner’s account.
Common tiers:
- Ordinary user, read-only. One approval, or pre-approved for assigned agents.
- Ordinary user, write. One approval from someone other than the requester.
- Customer admin or owner. Read-only only, a second approver, the shortest duration and an explicit reason.
- Staff or platform admin. Not available through support tooling. Use a separate emergency procedure.
Approval tiers don’t replace target rules. An approver who can’t see the target’s role can’t apply a tier, and an approval doesn’t make a forbidden target allowed.
How does Devora handle who may act as whom?
Devora handles the approval side. Each request is reviewed by an approver or admin in your organization. Admins can approve their own requests (opens in a new tab) unless you turn that off. Being someone’s manager doesn’t grant approval rights, and self-approval is recorded separately.
The target side is your application’s job, because Devora doesn’t know your roles. Devora’s search results guidance (opens in a new tab) is to exclude inactive and internal staff accounts from your search handler. You can return a role attribute so approvers see it in the request.
Then enforce it where the credential is issued. Your impersonation handler receives (opens in a new tab) the staff member’s ID and email, the target user, the scope and an authorizationSource field that distinguishes self-approved requests. Look up the target’s current role and refuse privileged targets, or refuse self-approved write sessions for customer admins. If the handler throws, the session fails to start (opens in a new tab) and the error message isn’t sent back, so log the reason on your side.
Devora has a single review step per request. It doesn’t add a second approver for privileged targets, and it has no staff-to-customer assignment feature. Tiers beyond that are code in your handler.
How do you test impersonation permissions?
Use synthetic accounts in staging. Create an ordinary support agent, a senior agent, an ordinary customer user, a customer owner, a staff account and a platform admin. These are pass conditions to check, not results we’ve run.
| Actor | Target | Requested | Expected |
|---|---|---|---|
| Ordinary support | Ordinary customer user | Read | Allowed after approval |
| Ordinary support | Customer owner | Read | Refused when the credential is issued |
| Senior support | Customer owner | Write | Refused; only read is allowed |
| Senior support | Customer owner, self-approved | Read | Refused if your policy needs a second approver |
| Any support | Staff account | Read | Not found in search; refused at issuance if forced |
| Any support | Platform admin | Read | Refused at issuance |
| Ordinary support | User promoted to owner after approval | Read | Refused, using the role at issuance |
| Agent already impersonating | Any user | Read | No new credential issued |
Check the denial on your backend, not only in the UI, and record which layer refused it. Then make sure your audit logs record the real staff member for every allowed session.
Write the policy table first, then the handler checks, then run the matrix. When you’re ready to try the approval side, create a Devora account and run one staging session against a synthetic owner account.
Sources
- Better Auth: Admin plugin (opens in a new tab)
- Keycloak Server Administration Guide: Fine-grained admin permissions (opens in a new tab)
- django-hijack documentation, 3.7.9: Customization (opens in a new tab)
- laravel-impersonate on GitHub (opens in a new tab)
- laravel-impersonate source, 1.7.8: ImpersonateController.php (opens in a new tab)
- Symfony documentation: How to impersonate a user (opens in a new tab)
- WorkOS: Impersonation (AuthKit) (opens in a new tab)
- Auth0: Session Delegation (opens in a new tab)
- Auth0: Delegated session behavior and monitoring (opens in a new tab)
- Symfony source, 8.1: SwitchUserListener.php (opens in a new tab)
- django-hijack source, 3.7.9: views.py (opens in a new tab)
- Devora: Overview for teams (opens in a new tab)
- Devora: FAQ
- Devora: Search results and templates (opens in a new tab)
- Devora: Backend integration (opens in a new tab)