What's wrong with a homegrown 'login as user' button

A homegrown login as user feature fails in predictable ways: no approval, no expiry, tokens in URLs, actions logged as the customer. Each failure and its fix.

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 yourselfUse a tool
Choose it whenA few engineers use it rarely, and you want full controlSupport uses it daily, and you want requests, approvals and access history without building them
Approvals and access reviewYou build the queue, the UI and the historyProvided, with your policies configured
Scope enforcementYou write it into your backendUsually a backend guard you install, plus rules you maintain
Tenant and eligibility checksYoursStill yours
Side effects in your appYoursStill yours
Ongoing costEngineering time as your app changesIntegration 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

Quick answers

Common questions, answered clearly.

Direct answers to the questions teams usually ask when evaluating this approach.

Is an admin login as user feature a security risk?

It can be. The feature lets one person act as another, so any gap in who can use it, how long it lasts or how it's logged becomes a path to account takeover or an unexplainable change. Built with an eligibility check, a separate staff session, an expiry and logs that record both identities, it is a reasonable support tool.

How should an admin login as user feature be logged?

Record the staff member and the customer on every event, plus the session, the reason and when access started and ended. If your logs only show the customer's user ID, you can't tell what the customer did from what your staff did while impersonating them.

Should admins be able to impersonate other admins?

Usually not. Impersonating an administrator can hand someone permissions they don't have, and chained impersonation confuses audit trails. Block it by default and allow it only through an explicit, reviewed exception.

Can I put the impersonation token in the URL?

Avoid it. Anything in a URL can end up in server logs, browser history, the Referer header and monitoring tools, even over HTTPS. If you need a redirect, use a short-lived, one-time code that is exchanged server-side and removed from the address bar straight away.

When is a homegrown login as user feature good enough?

When a few trusted engineers use it rarely, every use is logged with both identities, sessions expire on the server, and sensitive actions are blocked. Once support staff use it daily across many customers, approvals, scope and access review usually justify either more engineering time or a dedicated tool.

Put it into practice

Give your team context.Keep control of access.

Bring approvals, scoped sessions, and access history into your support workflow.