WRNexus
Back to the blog
security 5 min read

Impersonation, done safely

How the WRNexus staff console lets engineers help customers without ever silently spoofing their identity — and the cookie design behind it.

Priya Iyer

#security · #support · #audit

Impersonation is the third rail of support tools. Done well, it is the fastest path to reproducing a customer issue. Done poorly, it is a liability that lives in your auditor’s spreadsheet forever. Here is how WRNexus handles it.

The threat model

Three things we never want to happen:

  1. A staff engineer impersonates a customer without anyone — themselves included — being able to forget they did it.
  2. An attacker who steals a staff session can use it to spoof customers without an extra factor.
  3. A customer cannot tell, after the fact, who looked at their data and when.

The mechanic

When a staff member starts an impersonation session, the backend mints a new cookie distinct from their normal session. It looks roughly like this:

__Secure-wrn_imp=<imp_session_id>; Path=/; Domain=.wrnexus.com; HttpOnly; Secure; SameSite=Lax

Two non-obvious properties:

  • The cookie expires in 30 minutes by default and 4 hours maximum, hard-cap regardless of activity. There is no rolling renewal.
  • The cookie’s payload encodes the staff actor, not the customer. Every privileged request goes through a middleware that knows it is looking at an impersonation session and stamps every audit row with both the actor and the impersonated user id.

The staff member’s normal session is left intact. When the impersonation session expires (or the staff member clicks “End impersonation”), they return to their own identity with no further action.

The visible banner

The customer-facing UI changes the moment an impersonation cookie is present. The marketing-style site doesn’t see it, but every admin and account surface renders a high-contrast banner across the top:

Impersonating customer@example.com · Started by lena@wrnexus.com at 14:02 UTC · Ends in 27 minutes

The banner is hardcoded — not theme-able, not dismissible. If you forget you are in an impersonation session, you cannot miss it.

The audit trail

Every impersonation start writes one row:

{
  "action": "staff.impersonation.start",
  "actor": { "type": "staff", "id": "stf_01H…" },
  "target": { "type": "user", "id": "usr_01H…" },
  "metadata": { "reason": "Triaging billing issue #1234" }
}

Every action taken during the session writes a row with both actor (the staff member) and metadata.impersonated_user_id (the customer). The customer, when they later open Account → Activity, sees a list of every impersonation session — start time, end time, and the engineer who ran it.

We make a point of telling customers in our onboarding that they will see this. Surprises here are the worst kind.

The reason field

We require a free-text reason when an impersonation starts. It is shown in the audit row, in the customer’s activity feed, and in the staff console’s history. We deliberately do not validate it beyond “must be non-empty and under 240 characters” — its purpose is social, not technical. The act of typing “Triaging billing issue #1234” is what prevents the system being used for fishing.

If you are designing impersonation in your own product: make the cookie distinct, encode the actor not the target, surface a banner the customer cannot miss, and require a reason every time. The rest is plumbing.