BitzOrcas has two controlled support paths. User impersonation represents a user inside the same tenant. Tenant Impersonation lets a platform operator enter a customer tenant under an existing grant. Both retain the real operator, but they change different parts of the security context.
Two security paths
Normal users, delegated callers, and Host operators are distinct caller types. Business code consumes ICurrentUser and tenant context instead of parsing arbitrary headers or claims.
Scenarios and invariants
| Scenario | Changed context | Evidence retained |
|---|---|---|
| User impersonation | current business user | target, impersonator, tenant, reason, generation, expiry, and handoff mode |
| Tenant Impersonation | current tenant | operator, origin/target tenant, grant |
Both issuance services reject nesting. User impersonation requires the exact permission, a same-tenant non-Host target, a reason, and a 1–120 minute duration. It creates a DelegationGrant if no row exists and does not require target-user approval. Tenant Impersonation rejects a blank or self target and requires a currently valid cross-tenant grant.
Both short-token types have a two-hour ceiling. User impersonation uses the requested 1–120 minutes, while Tenant Impersonation is also capped by the remaining cross-tenant grant lifetime.
The two grants are established differently
Both paths treat a server-side grant as revocable authority and the token as its short-lived projection. They differ in how that grant is established.
- An administrator with
identity.user.impersonatestarts user impersonation directly. The service creates or rotates the row for that administrator-and-target pair and saves Reason, SessionId, ExpiresAt, and HandoffMode. - Cross-tenant Tenant Impersonation requires an existing authorization record that covers the operator and target tenant.
controlled start / cross-tenant approval → grant + generation → short token │ └─ re-check on every requestCalling the JWT builder must never bypass the Application Handler, exact permission, target ownership, or authoritative record. Break-glass access needs a separate, short, strongly authenticated, and strongly audited path.
Middleware order
The API authenticates the base identity, processes Delegation, resolves/guards the tenant, then processes Tenant Impersonation before audit and endpoint authorization.
Authentication → DelegationTokenMiddleware → TenantResolution / TenantGuard → TenantImpersonationTokenMiddleware → Audit / HTTP Authorization / EndpointsThe middleware maps incomplete claims or independent expiry to Delegation.Expired, and a missing, revoked, reissued, or unreadable authoritative grant to Delegation.Revoked; both are 401. Delegation.Unavailable remains in the Host error catalog but is not used by the current middleware path. A failure must not silently fall back to the original identity.
Delegation boundary
A successful token projects CallerType.Delegated and retains impersonator, grant, session, and independent expiry. Authorization caching is bypassed for delegated callers because generation rotation and revocation must not inherit a normal-user cache lifetime.
// Use unified context; do not parse delegation claims in each handler.var actor = currentUser.UserId;var realOperator = currentUser.ImpersonatorId;
// Audit records both the business actor and the real human/operator.audit.Record(actorId: actor, impersonatorId: realOperator);DelegationGrantStore now consumes IEntitySet<DelegationGrant> and the unified ICommandRepository, and shared integration tests register both SqlSugar and EF Core generic repositories. Delivery still needs provider contracts for the unique key, generation rotation, HandoffMode columns, and transactions; the presence of ImpersonatorId alone does not prove full parity.
Browser handoff supports TabScoped and SiteWide. TabScoped sends short-lived access only to the target tab. SiteWide synchronizes open same-origin tabs and uses a token-free SessionId pointer plus the administrator HttpOnly cookie for cold-start resume. The unconfigured tenant default is TabScoped, and a start request can override it.
Tenant Impersonation boundary
Tenant Impersonation is platform support access, not customer role switching. It retains operator ID and origin tenant while changing downstream tenant context. Queries still require target-tenant filtering and resource authorization.
// IssueAsync rejects blank/self targets, nesting, and missing/expired grants.var descriptor = await tokenService.IssueAsync( @operator, targetTenantId, cancellationToken);
// Expiry cannot exceed grant remainder or the two-hour ceiling.return descriptor.ExpiresAt;Entering a tenant does not grant customer-admin permissions. Dedicated support permissions, action restrictions, Feature entitlement, and DataScope still apply.
Restrict high-risk actions
Consider denying or step-up protecting MFA changes, key creation/export, role elevation, tenant deletion, payments, bulk PII export, and audit-retention changes during a represented session.
Enforce restrictions in policy or handler preconditions, not only by hiding UI controls.
public static class DelegationErrors{ public static readonly Error ForbiddenAction = Error.Forbidden( "Security.Delegation.ForbiddenAction", "This action cannot run in a delegated session.");}
// Make a non-delegable operation explicit at the server boundary.if (currentUser.CallerType == CallerType.Delegated) return Result.Failure(DelegationErrors.ForbiddenAction);User experience
Before entry, show target identity, tenant, reason, expiry, and handoff mode. Keep a persistent banner and immediate exit action. TabScoped must make administrator tabs explicit; SiteWide must synchronize adoption and exit and revoke the server session if handoff fails.
Clearing a local token is not enough. /exit revokes the generation named by the current JWT, the real administrator can DELETE the target session idempotently, and /restore then recovers administrator identity through the HttpOnly refresh cookie.
Audit evidence
Record issuance, use, denial, exit, expiry, and revocation with:
- operator, business actor, origin tenant, and target tenant;
- grant/token identifier, reason, ticket, and approver;
- action/resource, result, time, and client context;
- CorrelationId, TraceId, and affected aggregates;
- extra approval or denial for high-risk actions.
Never store the delegation token in logs or audit. Queries should reconstruct a full support session by operator, tenant, time, and ticket.
Test matrix
- user impersonation covers first-row creation, generation rotation, blank/self/Host/cross-tenant denial;
- duration accepts only 1–120 minutes; Tenant Impersonation remains capped by cross-tenant grant lifetime;
- both paths reject nesting;
- revocation denies the next request without identity fallback;
- authorization cache cannot extend delegated lifetime;
- tenant filter and DataScope still apply;
- audit retains actor and impersonator;
- TabScoped cannot resume, while SiteWide verifies cookie, Origin, platform, and SessionId;
- every persistence profile has an explicit support/rejection contract.
Current gap
The mainline has both token services and middleware, user start/current/exit/revoke/restore endpoints, dual handoff modes, tenant policy, a common sensitive-command denial, and audit capacity. Provider adoption must verify grant persistence, generation rotation, active revocation, the handoff schema migration, and audit query—not merely the presence of claims.
Apply 202608090007-impersonation-handoff-mode.sql before enabling the dual-mode frontend. The current source worktree also adds DEFAULT 'TabScoped' to the SqlSugar CodeFirst non-null mappings; release review should include that uncommitted source change alongside the revision documented here.