Claude Code Boilerplate
FeaturesPricingBlogDocs
Get started →

Product

  • Features
  • Pricing
  • Skills

Compare

  • vs ShipFast
  • vs MakerKit
  • vs supastarter

Resources

  • Docs
  • Blog
  • Discord

Legal

  • License
  • Privacy Policy
  • Terms of Service
Claude Code Boilerplate

© 2026 Claude Code Boilerplate. All rights reserved.

← All posts

User Impersonation in Your Next.js SaaS -- Let Support Staff Debug Any Customer Account Without Their Password

October 27, 2026
nextjsauthsaasdrizzle-ormadmin

When a paying customer says "the dashboard is broken for me," you have two choices.

You can ask for screenshots, walk them through troubleshooting steps, and spend 30 minutes on a problem you could solve in 90 seconds -- or you click "Login as User" and see exactly what they see.

User impersonation is one of those features every SaaS support team eventually needs. Most founders build it twice: once badly, then again after a security concern. Here is the safe way to do it from the start.

The problem impersonation solves

Your admin panel already lets you search for users and edit their data. That handles about half of support tickets.

The other half are "nothing works for my account" tickets -- a broken layout, a permission error, or an edge case in the user's specific data. You cannot reproduce it from the admin side. You need to see what they see.

Without impersonation, you are debugging blind. With it, you click a button, land in the user's session, reproduce the problem in 10 seconds, fix it, and close the ticket.

How secure impersonation works

The wrong approach: use the user's password to log in normally. This breaks your audit trail and exposes you to liability.

The right approach uses a short-lived, single-use impersonation token:

  1. An admin clicks "Login as User" in the admin panel
  2. Your API creates a one-time token linked to the target user ID and the admin ID
  3. The admin's browser opens a new tab with that token in the URL
  4. A dedicated endpoint validates the token, marks it used, and creates a normal JWT session for the target user
  5. The JWT stores which admin started the impersonation
  6. A banner at the top of every page shows "Viewing as user@example.com -- Exit impersonation"

This guarantees three things: the token expires in 5 minutes and is single-use; every impersonation is logged with who did it and when; the admin can read and act as the user -- not become them permanently.

The database change

You need one new table. It stores the token, the admin who created it, the user being impersonated, whether it has been used, and when it expires.

In Drizzle ORM this follows the same pattern as every other module in the Next.js SaaS Boilerplate: a schema file, a relations file, a types file, and a service that throws HttpError instead of returning error objects.

Your JWT payload gets one optional field: impersonatedBy. When that field is present, the session belongs to the target user but records which admin started it. Every write in that session goes to the user's data, and your audit log records the impersonation event alongside it.

Three API endpoints

POST /api/admin/impersonation -- admin-only, creates the token. Verify the caller is an admin, generate a cryptographically random 32-byte token, insert it with a 5-minute expiry, return the token. Never log the raw token value.

GET /api/impersonation/enter?token=xxx -- the token is the credential. Find it, verify it is not expired or already used, mark it used in the same transaction, create a short-lived JWT for the target user with impersonatedBy set, redirect to the app.

POST /api/impersonation/exit -- clears the impersonated JWT and sends the admin back to the admin panel.

The banner you must always show

Never let an impersonated session run without a visible indicator. Support staff occasionally forget they are viewing as a customer and take actions that affect real data. The banner is the safety net.

It reads the impersonatedBy field from the current JWT. If it is set, it renders at the top of every page and stays there:

{session?.impersonatedBy && (
  <div className="bg-yellow-500 px-4 py-2 text-sm font-medium text-black">
    Viewing as {session.email} --
    <button onClick={exitImpersonation} className="ml-1 underline">
      Exit impersonation
    </button>
  </div>
)}

Because the boilerplate already ships JWT auth with getUserFromRequest, Drizzle ORM, and an admin module, you are connecting three pieces that exist -- not building them from scratch.

Two actions to block

Password changes. An admin impersonating a user must not be able to change that user's password and lock them out. Check for impersonatedBy in your password-change service and throw HttpError(403) if it is present.

Account deletion. Same reason. The user needs to be able to return after the session ends.

Everything else is allowed -- that is the point.

The decision to make before you build

Decide who can impersonate whom before you write the first line of code.

The safest rule: only users with the admin role can impersonate, and they cannot impersonate other admins -- only regular members. This stops privilege escalation. An attacker who compromises a regular account cannot reach admin access through impersonation.

If your app uses the multi-tenancy module with orgRoleEnum, the check is one condition: callerRole === 'admin' && targetUser.role !== 'admin'.

A one-afternoon feature

User impersonation is a one-afternoon build on top of the auth and admin panel you already have. One table, three routes, one banner component.

The next time a customer says "something is wrong with my account," you open their session in 15 seconds and close the ticket in five minutes.

Get the Next.js SaaS Boilerplate and add impersonation before your first paying customer needs support.