BetterIAM
Authentication

Impersonation

Audited "view as" sessions that let support staff see exactly what a member sees, restricted to what both people may do and visible everywhere.

Support teams often need to see what a customer sees: a missing button, a permission that does not apply, a page that fails for one person. Asking for their password is unsafe, and granting yourself their roles changes their access. solves this with a short-lived session that acts as the member, while every action stays attributed to the administrator behind it.

Impersonation is off by default. It is designed to be hard to misuse: it is opt-in per organization, needs a recorded reason, cannot do anything sensitive, never exceeds the administrator's own rights, and is visible to the member, to policies, and in every audit record.

Enable it for a tenant

An organization turns impersonation on in its authentication policy, and grants iam:identities:impersonate to the people allowed to use it:

await iam.api.tenants.setAuthPolicy(ownerCredential, {
  tenantId,
  authPolicy: { ...currentPolicy, allowImpersonation: true },
});

setAuthPolicy replaces the whole policy, so carry the existing fields over. Without allowImpersonation, every impersonation attempt in the tenant fails with FEATURE_DISABLED.

Start a "view as" session

identities.impersonate opens a session as a member and returns its token. Call it from your support tooling when an administrator chooses a member and states why:

const { token, session, identity } = await iam.api.identities.impersonate(adminCredential, {
  tenantId,
  identityId: memberId,
  reason: 'Ticket 4821: export button missing',
  durationMs: 30 * 60_000, // one minute to eight hours; one hour by default
});

The call succeeds only when all of these hold:

  • the administrator holds iam:identities:impersonate on the member and authenticated recently;
  • the administrator acts through an ordinary session of their own, not an impersonation or assumed-role session (IMPERSONATION_RESTRICTED);
  • a reason of up to 512 characters is given;
  • the member is an active person who is neither the administrator, an owner, nor a root administrator (owners and root administrators fail with ACCESS_DENIED; service accounts cannot be impersonated);
  • if the member requires MFA, the administrator's own session passed MFA (MFA_REQUIRED);
  • the tenant's IP allowlist and network blocks allow the administrator's address.

Each impersonation is audited as identity:impersonate with the reason, the new session ID, and its expiry.

The token is returned in the body only

The HTTP handler never sets the impersonation token as a cookie. Replacing the administrator's own session cookie would strand their real session in that browser. Keep the token in memory in a separate context, such as a dedicated tab or window, and send it as a bearer token.

support/view-as.ts
import { createIamClient } from 'better-iam/client';
import type { iam } from './iam';

// A client that acts as the member for as long as the token lives.
const viewAs = createIamClient<typeof iam>({ token: () => impersonationToken });
const { results } = await viewAs.authorizeMany({ tenantId, checks });

What the session can do

An impersonation session acts as the member, with the method impersonation: requests made with its token are evaluated as the member's . Every authorization decision made through it is allowed only when both the member and the impersonating administrator may perform the action. Support staff therefore cannot use a more privileged member's session to act, or to grant themselves lasting access, beyond their own role. Reverse queries (listAccessible) list only what both could reach. An action the member could perform but the administrator could not is refused like any other denial (ACCESS_DENIED).

It cannot:

  • perform anything that requires recent authentication: password, email, factor, device, session, and ownership changes (IMPERSONATION_RESTRICTED);
  • re-authenticate, so it can never become recent;
  • assume roles, grant OAuth consent, or impersonate anyone else.

It inherits the administrator's MFA state (principal.mfa) but carries no fresh factor time (principal.mfaTime is absent), and it does not count toward the member's maxSessions cap. What an administrator does through it is not recorded as the member's own access usage.

How long it lasts

The session lasts durationMs (one hour by default, eight hours at most) and never beyond the administrator's own session. It ends the moment:

  • its duration passes or the administrator's session expires;
  • the administrator signs out, or their session is revoked;
  • the administrator is disabled or deleted;
  • the session itself signs out, or is revoked like any other session.

Who can see it

Impersonation is never hidden:

WhereWhat it shows
Audit recordsEvery record the session produces carries impersonatorId beside the member's actorId. Webhook events carry it too.
Policiesprincipal.impersonated is true and principal.impersonatorId names the administrator.
AssertionsSigned caller tokens for your other services, issued with assertions.issue, carry impersonatorId.
The memberThe session appears in their own auth.listSessions, and auth.listSecurityEvents names the administrator.

Policies can use this to keep certain actions out of reach of support entirely, whatever the member's roles allow. An like this one overrides every allow:

{
  effect: 'deny',
  actions: ['billing:*'],
  resources: ['*'],
  conditions: { Bool: { 'principal.impersonated': true } },
}

The framework integrations recognize impersonation too: a page that demands a fresh second factor or recent authentication redirects an impersonation session to your step-up page with reason=impersonation. See Next.js guards.

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page