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:
- A staff engineer impersonates a customer without anyone — themselves included — being able to forget they did it.
- An attacker who steals a staff session can use it to spoof customers without an extra factor.
- 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 bylena@wrnexus.comat14: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.