Yes, Better Auth supports user impersonation through its admin plugin (opens in a new tab). An admin calls impersonateUser to “log in as” a customer and stopImpersonating to return to their own account. If your support team only needs that, start there.
The plugin decides whether a signed-in admin can impersonate. It doesn’t decide which customers a support agent should open, why, for how long, or what they may change once inside. This guide covers the native behavior and what your application adds on top.
What the admin plugin provides
- Endpoints.
impersonateUser(POST /admin/impersonate-user) andstopImpersonating(POST /admin/stop-impersonating), available from the client asauthClient.admin.*and from the server asauth.api.*. - Permission checks. The admin plugin docs (opens in a new tab) say the caller must be authenticated with an account whose role has the
impersonatepermission. You can define custom roles withcreateAccessControl. - Admin protection. By default admins can’t impersonate other admins. Grant
impersonate-adminsto a role to allow it. The olderallowImpersonatingAdminsoption is deprecated. - Session length. One hour by default, set with
impersonationSessionDuration. The session is non-persistent, so it also ends when the browser session ends. - Attribution. The impersonated session row stores
impersonatedBy, the admin’s user ID.
How restoration works: when impersonation starts, Better Auth keeps the admin’s session token in a signed admin_session cookie. stopImpersonating deletes the impersonated session and restores the admin session from that cookie. If the cookie is missing, points to an admin session that no longer exists, or belongs to a different user than impersonatedBy, the plugin’s source code (opens in a new tab) fails the call with “Failed to find admin session”. Cookie settings, subdomains and proxies all affect this, so test it in your real deployment.
Besides impersonatedBy, the plugin has no impersonation audit trail or approval step. Those are yours to build or bring.
Common Better Auth impersonation errors
Most “impersonation not working” threads come down to three messages defined in the admin plugin source (opens in a new tab):
| Message | Usual cause |
|---|---|
| ”You are not allowed to impersonate users” | The caller’s role doesn’t grant impersonate. The plugin reads the role stored on the user and checks it against the roles you passed to the plugin, unless the user is listed in adminUserIds. |
| ”You cannot impersonate admins” | The target has an admin role, and the caller lacks impersonate-admins. |
| ”Failed to find admin session” | stopImpersonating couldn’t restore the staff session from the admin_session cookie. |
If you defined custom roles, pass them to the admin plugin on the server. A role that exists only in your own code won’t satisfy the check.
Give support a narrow role
Don’t make support agents full admins just so the impersonate button works. Create a support role with impersonate and only the other permissions it truly needs. Keep these as separate decisions:
- Impersonating an ordinary customer.
- Impersonating an admin or privileged account (
impersonate-admins). - Changing roles, banning users, or managing sessions and credentials.
Also check business eligibility before calling impersonateUser. Is this customer assigned to this agent? Is the account active? Is it an internal or excluded account? A user ID from the browser doesn’t answer any of these. The assigned-customer guide covers these checks.
During the session, keep the customer’s normal permissions. If support sees a feature only because its own role bypasses authorization, the investigation won’t explain the customer’s problem.
When the native plugin is enough
The admin plugin alone can work well if:
- A few trusted staff impersonate occasionally.
- Your backend already checks
impersonatedByand blocks risky operations during impersonated sessions. - The one-hour default, or your own value, fits your investigations.
- You log the admin ID next to the customer ID for any change made while impersonating.
- You don’t need per-request approval, a recorded reason or a separate access history.
Try lighter options first. A staging fixture often reproduces a generic bug, and a backend denial log can explain a permission problem without opening the account.
How Devora fits with Better Auth
Devora adds the workflow around impersonation: access requests with a reason, approval by an approver or admin in your organization, read-only or read-and-write scope, a time limit and access history. Better Auth still creates the browser session, as Devora’s auth provider overview (opens in a new tab) explains.
Three requirements from the Devora Better Auth guide (opens in a new tab) shape the integration:
The staff member must already be signed in to your app. They need an existing Better Auth session with impersonation permission. Signing in to Devora doesn’t replace that. If support staff don’t have accounts in your app, look at the custom one-time session bridge (opens in a new tab) instead.
You add a small binding route. Better Auth’s impersonation endpoint doesn’t accept extra context, so Devora’s verified context has to be attached afterwards. Devora doesn’t ship a packaged helper for this. Your route should:
- Authenticate the newly impersonated session and confirm it is impersonating.
- Consume a short-lived, one-time handle from your backend.
- Check that the handle’s target matches the session’s user and that
impersonatedByis set. We also recommend checking thatimpersonatedBymaps to the approved staff member. - Store the verified context against that session unchanged, so the backend guard can read it on each request.
| Step | Owner |
|---|---|
| Approve the request: actor, target, scope, time window | Devora |
| Authenticate staff and create the impersonated session | Better Auth, with your role configuration |
| Hold verified context behind a one-time handle | Your backend |
| Bind the context to the impersonated session | Your binding route |
| Enforce scope plus normal tenant checks | Devora backend guard, plus your authorization |
| End access and clean up | Your application, triggered by Devora’s lifecycle events |
Close the gap between session creation and binding. Between impersonateUser and a successful binding, an impersonated session exists without Devora context. If your context function returns nothing, Devora’s Node scope guard (opens in a new tab) treats the request as ordinary traffic. So your app must treat “impersonatedBy is set but no binding exists” as denied, and clean up the session if binding fails. Test this failure path before the happy path.
Expiry and restoration
Keep impersonationSessionDuration no longer than the longest Devora session you allow. The Better Auth guide (opens in a new tab) notes that the backend guard also checks each session’s own expiry, so a short approval is enforced even if the Better Auth session would last longer.
When access ends with the tab open, the Devora callback calls stopImpersonating. Confirm it really returns the staff account, given what can make the admin_session cookie restore fail. If it fails, send the user to sign-in rather than leaving the customer session active.
A browser callback can’t run after the tab closes. Handle Devora’s signed termination notice (opens in a new tab) on your server and revoke the impersonated session there.
Test it in staging
Record your Better Auth version and use synthetic accounts. These are the outcomes to look for:
| Test | Pass condition |
|---|---|
Staff role lacks impersonate | No customer session is created |
| Support role targets an admin account | Denied unless impersonate-admins and your policy allow it |
| Handle reused, expired or bound to the wrong user | Binding fails; protected requests stay denied |
impersonatedBy doesn’t match the approved staff member | Binding rejected |
| Binding fails after the session is created | No unrestricted access; session cleaned up |
| Read-only session sends a write directly to the API | Backend rejects it; data unchanged |
| Access ends with the tab open, and with it closed | Customer access stops; staff session restored or sent to sign-in |
Follow the Devora testing guide (opens in a new tab) and record API responses and server-side session state, not just what the browser shows. Use the native plugin alone if it meets your requirements. If you need approvals, scoped access and access history, and you can own the binding route, create a Devora account and evaluate one staging investigation. The build vs buy guide can help with that decision.
Sources
- Better Auth: Admin plugin (opens in a new tab)
- Better Auth source: admin plugin (opens in a new tab)
- Devora: Auth providers (opens in a new tab)
- Devora: Better Auth guide (opens in a new tab)
- Devora: Custom session bridge (opens in a new tab)
- Devora: Node SDK reference, scope guard (opens in a new tab)
- Devora: Session lifecycle (opens in a new tab)
- Devora: Testing guide (opens in a new tab)