>_

View as user

Tier-2 org admins can answer "what does this user see right now?" without engineering shadowing them. Use cases:

  • Support: a user reports "I can't see transaction 0x…"; the admin reproduces the user's view and either finds the missing grant or rules out a UI bug.
  • Audit walk-through: an auditor inspecting an access-control decision asks "what would this admin's revoked user actually have been able to read?". The admin walks the answer instead of explaining it.
  • Pre-flight a grant change: before flipping a contract_grants row, confirm what the affected user sees with and without it.

The mechanism is a parallel URL tree on the Open Privacy Suite that always names an explicit organization: /api/v1/admin/impersonate/<target_did>/in/<org_id>/…. The block-explorer dashboard wraps it in a "View as user" button + amber banner. No user-shaped JWT is ever minted; the admin's own session keeps its admin identity throughout.


Which org you view as

View-as always resolves against one explicit organization — the org named in the URL.

In the dashboard, that org is the one currently selected in the org-context dropdown. When an admin clicks View as user, the session is anchored to that org: the impersonated view shows what the target user sees in that org, evaluated against that org's grants.

This matters for admins who are tier-2 in more than one org. If you administer both Org A and Org B, and the target user is a member of both, then:

  • Selecting Org A in the dropdown shows the user's Org A view (Org A's grants decide what's visible).
  • Selecting Org B shows the user's Org B view, which can differ when the underlying grants diverge.

The org is never guessed. If no org is selected, the dashboard does not start a View-as session, and a hand-crafted request without an org returns 400 "org_id is required". There is no silent "first matching org" fallback — the org you see is always the org you picked.


Who can use it

RoleCan View-as?Why
Super-admin (X-Admin-Token)NoSuper-admin has no data-layer reach today and View-as would be the path that gives it to them. Explicitly rejected.
Tier-2 org admin (is_org_admin = true)Yes, same-org targets onlyThis is the intended audience.
Tier-3 admin (admin claim only, no is_org_admin)NoPer-contract admin scope; cannot impersonate.
Read-only adminNoROA's whole purpose is read-only audit of admin actions, not user-data browse.
Regular userNoThe button is not rendered; the BFF rejects the start request; the Open Privacy Suite rejects the URL.

The dashboard shows the View as user button only on user-detail pages that the admin can already access. Each click mints a short-lived opaque session token; the URL the admin's browser receives carries the token, not the target user's DID.


What the impersonator can do

Strictly read-only

Write methods are blocked at three layers: the FE disables write controls with an "amber tooltip" while the banner is up; the BFF rejects any forwarded write-method body server-side; the Open Privacy Suite's impersonation surface returns 405 on non-GET requests. There is no path by which an impersonated session can move funds, deploy a contract, or mutate RBAC state.

While the banner is up:

  • Explorer pages (blocks, txs, addresses, logs, transfers, tokens) render with the target user's redaction set, not the admin's.
  • Read-only RPC calls (eth_call, eth_getBalance, eth_getCode, eth_getLogs, eth_getStorageAt, eth_getTransactionByHash, eth_getTransactionReceipt, and the org-free metadata set) are routed through the user's permission view.
  • Write-method UI (deploy, send-tx, grant-mutation, RBAC-edit) is disabled with a tooltip, not hidden — admins know the feature exists and is intentionally unavailable.

What the admin sees vs what the user sees: the redaction layer and visible dataset are identical to the target user's normal session. Logs the target cannot read are removed; addresses they cannot resolve are returned redacted; contracts they cannot access are denied. The admin's own broader visibility is not merged into the session. Clicking Stop viewing as restores the normal admin view.


Org boundary

Two independent checks gate every View-as request, in this order:

  1. The admin must administer the named org. The <org_id> in the URL must be one of the caller's own admin_org_ids. If it is not, the request returns 403 — the admin is naming an org they do not administer. (A tier-2 admin always knows which orgs they administer, so there is nothing to hide here.)
  2. The target must be a member of the named org. If the target user is not a member of <org_id> — whether because the user does not exist, or exists only in some other org — the request returns 404 "user not found", identical to the response for a never-seen DID.

The dashboard's user-list also filters out users who are not members of the selected org, so they never appear as a clickable target.

The 404-uniformity is by design (audit-scope item M3): a tier-2 admin in Org A must not be able to use View-as to enumerate which DIDs exist in Org B. Note the deliberate split — naming an org you don't administer is a 403 (an authorization error about your claims), while naming a target who isn't in an org you do administer is a 404 (an existence-hiding error about someone else's membership).


Audit

Every impersonated request writes one row to impersonation_log (migration 047). The row records:

ColumnValue
actor_didThe admin's DID (from their JWT)
impersonated_didThe target user's DID
org_idThe explicit org named in the View-as URL for this session
methodThe HTTP method + path that ran (e.g. GET /api/v1/explorer/blocks/123)
params_hashSHA-256 of the request query string — raw query bytes are never persisted
decisionallow (2xx), deny (4xx), or error (5xx)
reasonhttp_<status> on non-allow rows
correlation_idJoins back to the access log row for the same request

The audit row is written after the handler runs, so the decision reflects the actual response. If the audit write fails, the response status code is logged via slog.Error for SIEM correlation — there's no "silent View-as" path.

Cross-org targets and never-seen-DIDs (404 before handler dispatch) intentionally do not write audit rows. Pre-handler 404s would otherwise let a tier-2 admin spam-probe arbitrary DIDs without leaving an audit trail that distinguishes "user exists in a different org" from "user does not exist". Both collapse to silence in the log, mirroring the response shape.


How sessions expire

Sessions are time-bounded. Tokens issued by the BFF expire after 1 hour by default. After expiry:

  • The banner disappears with a toast notification.
  • The next API call from the impersonated context returns 401 "session expired".
  • The admin is back to their own view; no further action required.

The admin can also click Stop viewing as at any time. This deletes the token from the BFF store and reverts the URL to remove the ?as=… parameter.


Operator checklist

SettingDefaultNotes
Open Privacy Suite: impersonation surfaceenabled when admin auth is configuredNo env var — gated entirely by the existing tier-2 admin JWT pipeline.
Block-explorer BFF: session TTL1 hourAdjustable via IMPERSONATION_TOKEN_TTL; do not exceed 4 hours.
Block-explorer BFF: session-store backendin-memorySwap to Redis-backed store before scaling beyond a single BFF replica.
Audit log retentionmatches impersonation_log (forever-append)Subject to the chain integrity policy in Audit Log Integrity.

  • Dry-run RPC API — programmatic counterpart that also accepts write methods (translated to debug_traceCall against a discarded state).
  • Audit Log Integrity — how the impersonation_log chain is sealed.
  • RBAC overview — the same access decisions that gate normal user calls gate impersonated ones, by design.