[GH-ISSUE #6448] Prevent admins from impersonating other admins #27844

Closed
opened 2026-04-17 19:04:35 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @rclmenezes on GitHub (Dec 1, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6448

Is this suited for github?

  • Yes, this is suited for github

Currently, if an admin impersonates another admin, then it is difficult to audit what actions were taken during impersonation.

For example:

  • Admin A impersonates Admin B
  • As Admin B, they then impersonate customer User C
  • As User C, they perform an action

If you are logging sessionIds in stdout for audit purposes, then you read impersonatedBy in the sessions table to see if which user impersonated User C... but it will say Admin B and not Admin A.

Theoretically, you could look in the session table to see if Admin B had been impersonated in that timeframe, but it will be inconclusive on whether or not that particular action was done by Admin B or someone impersonating Admin B.

Describe the solution you'd like

I think the easiest solution is to simply prevent admins from impersonating other admins because it would prevent permission escalation out of the box.

There are very few use cases where we want admins to impersonate other admins. However, if some existing users want this capability, then we can make an allowAdminImpersonation option in the admin plugin that defaults to false. We should probably make the settings "as secure as possible" by default.

Describe alternatives you've considered

An existing stop-gap solution would be to use hooks:

hooks: {
      before: createAuthMiddleware(async (ctx) => {
        console.log("ctx.path is", ctx.path);
        if (ctx.path.includes("/admin/impersonate-user")) {
          const body = ctx.body as { userId?: string } | undefined;
          const userId = body?.userId;

          if (userId) {
            const [targetUser] = await db.select().from(users).where(eq(users.id, userId)).limit(1);

            if (targetUser?.role === "admin") {
              throw new Error("Cannot impersonate another admin");
            }
          }
        }
    }),
    ...
}

However, I don't think this is a good solution. We can't expect users to write this hook every time and we should try to make everything "secure by default".

Another alternative is to make an option in the admin plugin called "canImpersonate, which could be typed as (opts: { requestorUserId: string, targerUserId: string }) => Promise`. If we go this route, we could add a default implementation that prevents admin impersonation. The nice thing about this approach is that it solves #3056.

Additional context

No response

Originally created by @rclmenezes on GitHub (Dec 1, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6448 ### Is this suited for github? - [x] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. Currently, if an admin impersonates another admin, then it is difficult to audit what actions were taken during impersonation. For example: - Admin A impersonates Admin B - As Admin B, they then impersonate customer User C - As User C, they perform an action If you are logging sessionIds in stdout for audit purposes, then you read `impersonatedBy` in the sessions table to see if which user impersonated User C... but it will say Admin B and not Admin A. Theoretically, you _could_ look in the session table to see if Admin B had been impersonated in that timeframe, but it will be inconclusive on whether or not that particular action was done by Admin B or someone impersonating Admin B. ### Describe the solution you'd like I think the easiest solution is to simply prevent admins from impersonating other admins because it would prevent permission escalation out of the box. There are very few use cases where we want admins to impersonate other admins. However, if some existing users want this capability, then we can make an `allowAdminImpersonation` option in the admin plugin that defaults to false. We should probably make the settings "as secure as possible" by default. ### Describe alternatives you've considered An existing stop-gap solution would be to use hooks: ```typescript hooks: { before: createAuthMiddleware(async (ctx) => { console.log("ctx.path is", ctx.path); if (ctx.path.includes("/admin/impersonate-user")) { const body = ctx.body as { userId?: string } | undefined; const userId = body?.userId; if (userId) { const [targetUser] = await db.select().from(users).where(eq(users.id, userId)).limit(1); if (targetUser?.role === "admin") { throw new Error("Cannot impersonate another admin"); } } } }), ... } ``` However, I don't think this is a good solution. We can't expect users to write this hook every time and we should try to make everything "secure by default". Another alternative is to make an option in the `admin` plugin called "canImpersonate`, which could be typed as `(opts: { requestorUserId: string, targerUserId: string }) => Promise<Boolean>`. If we go this route, we could add a default implementation that prevents admin impersonation. The nice thing about this approach is that it solves #3056. ### Additional context _No response_
GiteaMirror added the lockedenhancement labels 2026-04-17 19:04:35 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 1, 2025):

(tag @better-auth-agent if you would like an answer from the Agent)

If you need more help, tag @Skyvern in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3597746109 --> @better-auth-agent[bot] commented on GitHub (Dec 1, 2025): (tag @better-auth-agent if you would like an answer from the Agent) _If you need more help, tag @Skyvern in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@ray73864 commented on GitHub (Feb 18, 2026):

Is there anyway to turn this into a flag?

It's caused me to become stuck on the fact that I can't impersonate an admin, which for my particular use case is an important thing, since I'm making the sales rep manager an admin so they can add other sales reps, but our overall manager of the sales rep manager would like to be able to impersonate anyone (including other admin) to see their dashboards.

I can work around it, but it's a nasty workaround, as it will involve either url params / query strings, or cookie updates, etc...

<!-- gh-comment-id:3919100152 --> @ray73864 commented on GitHub (Feb 18, 2026): Is there anyway to turn this into a flag? It's caused me to become stuck on the fact that I can't impersonate an admin, which for my particular use case is an important thing, since I'm making the sales rep manager an admin so they can add other sales reps, but our overall manager of the sales rep manager would like to be able to impersonate anyone (including other admin) to see their dashboards. I can work around it, but it's a nasty workaround, as it will involve either url params / query strings, or cookie updates, etc...
Author
Owner

@jslno commented on GitHub (Feb 18, 2026):

Hey @ray73864,
Just to let you know, I’ve opened a PR (#8045) to address the issue you’re facing

<!-- gh-comment-id:3921135322 --> @jslno commented on GitHub (Feb 18, 2026): Hey @ray73864, Just to let you know, I’ve opened a PR (#8045) to address the issue you’re facing
Author
Owner

@rclmenezes commented on GitHub (Feb 18, 2026):

Also, there is a flag:

admin({
  allowImpersonatingAdmins: true,
});
<!-- gh-comment-id:3921183641 --> @rclmenezes commented on GitHub (Feb 18, 2026): Also, there is a flag: ```ts title="auth.ts" admin({ allowImpersonatingAdmins: true, }); ```
Author
Owner

@ray73864 commented on GitHub (Feb 18, 2026):

Also, there is a flag:

admin({
allowImpersonatingAdmins: true,
});

I think that could be better referenced in the Impersonating User documentation / example.

I had to scroll down on the sidebar on the right hand side before I found it. My immediate eye sight was taken to the fact that 'Impersonate User' was easily visible on the right hand sidebar along with 'Stop Impersonating User'.

Google searching didn't initially bring it up either, but what it did bring up was this github issue.

The 'Impersonate User' description makes reference to a 'impersonationSessionDuration' option which isn't actually set during the impersonating user but is set on the admin plugin itself, so all I ask then is that the 'allowImpersonatingAdmins' one is also referenced in it.

<!-- gh-comment-id:3922839427 --> @ray73864 commented on GitHub (Feb 18, 2026): > Also, there is a flag: > > admin({ > allowImpersonatingAdmins: true, > }); I think that could be better referenced in the Impersonating User documentation / example. I had to scroll down on the sidebar on the right hand side before I found it. My immediate eye sight was taken to the fact that 'Impersonate User' was easily visible on the right hand sidebar along with 'Stop Impersonating User'. Google searching didn't initially bring it up either, but what it did bring up was this github issue. The 'Impersonate User' description makes reference to a 'impersonationSessionDuration' option which isn't actually set during the impersonating user but is set on the admin plugin itself, so all I ask then is that the 'allowImpersonatingAdmins' one is also referenced in it.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27844