A homegrown “login as user” feature usually works on day one and fails later in the same few ways: anyone with an admin flag can open any account, sessions never end, and the logs say the customer did what your staff did. None of these are exotic bugs. They come from building the sign-in step and stopping there.
This is a catalogue of those failure modes, with why each one happens and how to fix it. It’s written for teams who already have an admin login as user button, or are about to add one. If you’re new to the idea, start with what user impersonation is.
What is a homegrown “login as user” feature?
A homegrown login as user feature is an internal tool that lets staff open your app as a specific customer, typically by setting the session’s user ID to the customer’s. The shortcut is the problem. Swapping one user ID for another gives you the customer’s view, but none of the controls that should come with it: who may do this, for how long, with what scope, and with whose name on the record.
Here are ten ways that shows up, grouped by the question each one fails to answer.
Who can log in as whom?
Most serious incidents start here, because the check either doesn’t exist or lives in the wrong place.
1. Shared superuser access with no approval
Why it happens: the button sits behind an is_admin flag, so everyone with that flag can open any account at any time, with nobody else involved.
The fix: give the permission to named people, require a reason for each use, and add an approval step for real customer accounts. Even a lightweight request-and-approve flow gives you a moment where someone else can say no.
2. No eligibility or tenant check
Why it happens: the impersonation endpoint checks the caller’s role but not whether that caller may reach that specific customer. Some libraries leave this open by default. The laravel-impersonate README (opens in a new tab) says that by default all users can impersonate and all users can be impersonated, until you add canImpersonate() and canBeImpersonated() to your user model.
It gets harder when the rule depends on both people. In a Keycloak discussion on fine-grained impersonation (opens in a new tab), a developer wanted service providers to impersonate only their own users, but couldn’t find a way for the policy to see both the impersonator and the target at once.
The worst version is impersonation driven by request parameters. LevelBlue SpiderLabs described applications in When User Impersonation Features In Applications Go Bad (opens in a new tab) where ordinary pages accepted parameters such as an impersonate flag and a user ID. A normal user who found them could load another user’s profile, change its email address and take over the account through password reset.
The fix: decide eligibility in one server-side place, using both identities: is this staff member allowed to reach this customer, in this tenant? Never read the target user or tenant from a browser-supplied parameter.
3. Admins can impersonate admins
Why it happens: the target check only excludes the caller, so an admin can become another admin, or a more privileged one. In Better Auth issue #6448 (opens in a new tab), opened in December 2025, a user showed how chained impersonation breaks auditing: admin A impersonates admin B, then customer C, and the session record names B as the impersonator. Better Auth blocked impersonating admins by default in pull request #6454 (opens in a new tab), merged that month, with an option to allow it.
The fix: block impersonation of privileged accounts by default, and block starting a new impersonation from inside an impersonated session.
Whose session is it, and what gets logged?
A login as user session should belong to the staff member, even though the app renders the customer’s account.
4. The staff session is replaced by the customer’s
Why it happens: the simplest implementation overwrites the staff member’s session with the customer’s. From then on, the app has no idea a staff member is involved, and the staff member’s own session is gone.
The fix: create a separate impersonation session that carries both identities, often called the actor and the subject. An Auth0 employee, answering a 2025 community question about impersonation alternatives (opens in a new tab), suggested a similar shape: a separate help desk app, with API middleware that confirms whether the caller is a help desk representative or the user themselves. Also issue a fresh session ID when access starts, as the OWASP Session Management Cheat Sheet (opens in a new tab) recommends after any privilege level change.
5. Actions are logged as the customer
Why it happens: your audit events record user_id, and during impersonation that’s the customer. A staff member changes a setting and the log says the customer did it.
The fix: add the actor to every event written during impersonation. The OWASP Logging Cheat Sheet (opens in a new tab) asks applications to record when, where, who and what for each event. “Who” has two answers here, and you need both.
6. There is no record of why
Why it happens: the button takes one click, so nobody types a reason. Months later, nobody can say why someone opened that account.
The fix: require a reason, ideally tied to the ticket, and store it with the session. It costs a few seconds per use and answers the first question in any access review.
How does it end?
An impersonation session needs a hard stop on the server, and a way to stop it sooner.
7. No expiry
Why it happens: the impersonated session inherits the customer’s normal session lifetime, which may be days or weeks.
The fix: give impersonation its own short, server-enforced expiry. The session management guidance (opens in a new tab) says all sessions should have an absolute timeout regardless of activity. For impersonation, that window should match the investigation, not the customer’s convenience.
8. No way to end it early
Why it happens: “stop impersonating” is a button in the staff member’s browser. If they close the tab, leave the company or the approval is withdrawn, nothing reaches the session.
The fix: make sessions revocable on the server by session ID, and check that revocation on every request. Test it with the tab still open: the next protected request should fail.
Where do tokens and side effects leak?
The last two failures happen outside the session itself, in URLs and in the systems your app talks to.
9. Impersonation tokens in URLs
Why it happens: a link like /impersonate?token=... is the easiest handoff between an admin panel and the app. OWASP’s page on information exposure through query strings (opens in a new tab) lists where those values end up: Referer headers, web logs, shared systems, browser history and caches. In its words, “Simply using HTTPS does not resolve this vulnerability.”
The fix: if you must redirect, pass a short-lived, one-time code instead of the credential, exchange it on the server, and remove it from the address bar immediately. Keep it out of analytics and error reports.
10. Side effects: emails, analytics and billing
Why it happens: your app doesn’t know it’s being impersonated, so it behaves normally. Opening a page can send a notification, count as an active user, update “last seen”, trigger an onboarding email or start a trial clock. The laravel-impersonate README (opens in a new tab) gives overriding analytics and tracking events during impersonation as one reason to use a package rather than a bare login call.
The fix: make the impersonation context available everywhere your app sends email, records analytics or touches billing, and suppress or relabel those events. Default to read-only, and review routes that look harmless but change state, such as a GET that marks something as sent.
Should you fix it yourself or use a tool?
You can fix all ten yourself, and for a small team that may be the right call. Use this checklist either way.
- Only named staff can impersonate, and each use has a reason
- Eligibility is checked server-side, using both the staff member and the target
- Privileged accounts can’t be impersonated by default
- Impersonation creates a separate session carrying both identities
- Every audit event records the staff actor as well as the customer
- Sessions have a short server-side expiry and can be revoked by ID
- No credential appears in a URL, log or analytics event
- Email, analytics and billing know about impersonation
- Read-only is the default and is enforced on the backend
| Fix it yourself | Use a tool | |
|---|---|---|
| Choose it when | A few engineers use it rarely, and you want full control | Support uses it daily, and you want requests, approvals and access history without building them |
| Approvals and access review | You build the queue, the UI and the history | Provided, with your policies configured |
| Scope enforcement | You write it into your backend | Usually a backend guard you install, plus rules you maintain |
| Tenant and eligibility checks | Yours | Still yours |
| Side effects in your app | Yours | Still yours |
| Ongoing cost | Engineering time as your app changes | Integration time plus the tool’s cost |
The last two rows matter. No tool knows your tenant model or which of your routes sends email, so some of the work stays with you whatever you choose. For more on the controls themselves, see the multi-tenant impersonation checklist, and for how one auth library handles them, the Better Auth impersonation guide.
How Devora handles these failure modes
Devora covers the workflow parts of this list. Staff request access with a reason, scope and duration, and an approver or admin in your organization reviews it before your backend creates the login. The backend guard enforces read or write scope and rejects missing, malformed or expired impersonation context, as described in Devora’s scope and security guide (opens in a new tab). Sessions end when the agent ends them, an admin terminates them, the approval is revoked, or the time limit or idle timeout is reached. Devora then calls your backend’s terminate handler, retrying failed deliveries, even when no tab is open, per the session lifecycle guide (opens in a new tab).
For the URL problem, the redirect carries a one-time exchange code, not your login token, and the frontend SDK removes it from the URL immediately (opens in a new tab). Access history keeps the staff member, target, reason, scope and decisions.
Some items stay yours. Your backend decides which users can be found and impersonated, so tenant eligibility and blocking admin targets are your code. Devora has no staff-to-customer assignment feature. Your app also owns credential issuance and revocation, its own audit events and suppressing side effects like emails. If that split works for you, create a Devora account and try one staging session against the checklist above.
Sources
- 404labfr: laravel-impersonate README (opens in a new tab)
- Keycloak GitHub discussion #29902: Fine Grained Impersonations (May 28, 2024) (opens in a new tab)
- LevelBlue SpiderLabs: When User Impersonation Features In Applications Go Bad (May 18, 2023) (opens in a new tab)
- Better Auth issue #6448: Prevent admins from impersonating other admins (December 1, 2025) (opens in a new tab)
- Better Auth pull request #6454: Prevent impersonating admins by default (merged December 9, 2025) (opens in a new tab)
- Auth0 Community: Impersonation in 2025 (July 31, 2025) (opens in a new tab)
- OWASP: Session Management Cheat Sheet (opens in a new tab)
- OWASP: Logging Cheat Sheet (opens in a new tab)
- OWASP: Information exposure through query strings in URL (opens in a new tab)
- Devora: Scope and security (opens in a new tab)
- Devora: Session lifecycle and cleanup (opens in a new tab)
- Devora: FAQ, redirect code vs login token
- Devora: Frontend integration (opens in a new tab)