DEV Community

Cover image for 👥 How I Built a Secure and Clean User Impersonation Feature (ReactJS + NodeJS)
Akash Shukla
Akash Shukla

Posted on • Edited on

👥 How I Built a Secure and Clean User Impersonation Feature (ReactJS + NodeJS)

Hello Everyone, Welcome! Today I'll share something really useful.

Have you ever needed to see exactly what a user is seeing — without asking for their password?
Whether you're debugging an issue, providing customer support, or verifying role-based access — sometimes, the best way to help is by temporarily stepping into the user's shoes.

That’s where user impersonation comes in — and in this article, I’ll show you how to build it securely and cleanly using React, Node.js, and JWT.

What is user impersonation?
It’s a feature that allows admins to temporarily "become" another user, so they can:

  • Debug issues in the app
  • Help users in real-time
  • View permissions and flows as that user

In this article, I’ll walk you through how I built a secure impersonation system using:

  • ✅ JWT (JSON Web Token)
  • ✅ React (Frontend)
  • ✅ Node.js + Express (Backend)
  • ✅ HTTP Only Cookies for session control

🧠 Understanding the Concept First

Let’s take a real-life example:

Imagine you’re a Admin of an App. A customer says:

"I can’t find the report tab on my dashboard."

Now, instead of guessing or asking for screenshots, you can:

  1. Click "Impersonate User"
  2. Instantly log in as that user
  3. See the exact issue
  4. Help them — fast!

But impersonation also has risks, so it must be:

  • Secure: Only admins should do it
  • Trackable: You should know who did what
  • Reversible: Admin can exit anytime

🔄 Flow Diagram

🧭 Step-by-Step Impersonation Flow

  1. Admin clicks "Impersonate" on the user list.
  2. 🔐 Backend generates a special JWT containing both:
    • Target user’s data
    • isImpersonating: true and impersonatedBy (admin info)
    • The backend sets this JWT as an HTTP-only cookie, ensuring it's securely managed by the server and not accessible via client-side JavaScript.
  3. 🍪 Frontend sets the isImpersonating: true and redirects to the impersonated user’s dashboard.
  4. 👀 Dashboard detects impersonation flag (from cookies) and shows a banner like: "You're impersonating John Doe".
  5. 🚫 Admin clicks "Stop Impersonation", which:
    • Calls stop API
    • Resets JWT back to the original admin
    • Redirects to admin dashboard

🛠️ Let's Implement It (Step-by-Step)


🔹 Step 1: Trigger Impersonation from React

We hit an API to start impersonating a user and save the JWT in cookies.

const startUserImpersonation = (userId: string) => {
  impersonateMutation.mutate(userId, {
    onSuccess: ({ data }) => {
      const { user, isImpersonating } = data;

      saveUserCookies({ roles: user.roles });
      Cookies.set('IMPERSONATION_ACTIVE', String(isImpersonating));

      toast.success("Impersonation started.");
      router.replace(resolveUserHome(user.roles)); 
    },
    onError: () => toast.error("Failed to impersonate user."),
  });
};
Enter fullscreen mode Exit fullscreen mode

🔹 Step 2: Backend API to Start Impersonation

async function startImpersonation(req, res) {
  const user = await UserModel.findById(req.params.id);
  if (!user || user.roles.includes("admin")) {
    return res.status(400).json({ message: "Cannot impersonate this user." });
  }

  const tokenPayload = {
    _id: user._id,
    email: user.email,
    roles: user.roles,
    impersonationActive: true,
    impersonatedBy: {
      _id: req.user._id,
      email: req.user.email,
      roles: req.user.roles,
    },
  };

  const token = createJWT(tokenPayload, 1); // 1 hour expiry time
  res.cookie("auth_token", token, jwtCookieOptions);

  res.status(200).json({ message: "Impersonation started", data: { user, isImpersonating: true } });
}
Enter fullscreen mode Exit fullscreen mode

Note: Make sure this API is protected by appropriate middleware, so that only authorized users (e.g., admins) have access to perform impersonation.


🔹 Step 3: Show "Stop Impersonation" Banner in React

{isImpersonating && (
  <div className="fixed top-2 left-1/2 -translate-x-1/2 bg-gray-900 text-white px-4 py-2 rounded">
    You're impersonating {userName}
    <button onClick={stopImpersonation} className="ml-2 underline">Stop</button>
  </div>
)}
Enter fullscreen mode Exit fullscreen mode

And here’s the handler:

const stopImpersonation = () => {
  stopImpersonationMutation.mutate(undefined, {
    onSuccess: ({ data }) => {
      saveUserCookies({ roles: data.user.roles });
      Cookies.remove('IMPERSONATION_ACTIVE');
      toast.success("Impersonation stopped.");
      router.replace(resolveUserHome(data.user.roles));
    },
  });
};
Enter fullscreen mode Exit fullscreen mode

🔹 Step 4: Middleware to Decode JWT

Here we check if the token has impersonation info.

async function decodeAuthToken(req, res, next) {
  const token = req.cookies.auth_token;
  if (!token) return next();

  const decoded = await verifyJWT(token);

  if (decoded.impersonationActive) {
    req.user = decoded.impersonatedBy;
    req.isImpersonating = true;
    return next();
  }

  // Normal user session
  req.user = await UserModel.findById(decoded._id);
  next();
}
Enter fullscreen mode Exit fullscreen mode

🔹 Step 5: Stop Impersonation on the Backend

async function stopImpersonation(req, res) {
  if (!req.isImpersonating) {
    return res.status(400).json({ message: "Not currently impersonating." });
  }

  const adminUser = await UserModel.findById(req.user._id);
  const token = createJWT(adminUser);
  res.cookie("auth_token", token, jwtCookieOptions);

  res.status(200).json({ message: "Impersonation ended", data: { user: adminUser } });
}
Enter fullscreen mode Exit fullscreen mode

✅ Bonus Tips to Improve It

To take this feature from “cool” to “production-ready,” here are some pro tips:


📌 Add Logs

Log who impersonated whom:

logger.info(`${req.user.email} impersonated ${user.email} at ${new Date()}`);
Enter fullscreen mode Exit fullscreen mode

🚫 Block Dangerous Actions

For example, disallow deleting users while impersonating:

if (req.isImpersonating) {
  return res.status(403).json({ message: "This action is not allowed during impersonation." });
}
Enter fullscreen mode Exit fullscreen mode

🧾 Save Audit Trails

(Optional) Store impersonation events in DB:

  • Who started
  • When
  • Actions taken

🧩 Final Thoughts

This impersonation system helps:

  • Debug user issues faster
  • Support customers better
  • View the app like your users do

It’s simple, powerful — and when done securely — a huge win for your team and product.

Top comments (0)