fullstackhero/dotnet-starter-kit · error · ForbiddenException

cross-tenant impersonation is restricted to platform…

Error message

cross-tenant impersonation is restricted to platform operators

What it means

The handler allows impersonation only within the actor's own tenant, unless the actor belongs to the root (platform) tenant. Any other cross-tenant attempt throws ForbiddenException('cross-tenant impersonation is restricted to platform operators'), mapping to HTTP 403.

Solutions

  1. Use an account in the root tenant (platform operator) for cross-tenant impersonation
  2. Target users within your own tenant instead
  3. Ensure TargetTenantId exactly matches the actor's tenant (ordinal, no whitespace)

Example fix

// before
await api.startImpersonation({ targetUserId, targetTenantId: otherTenantId }); // 403
// after
const targetTenantId = myTenantId; // stay in-tenant, or use a root-tenant operator token
await api.startImpersonation({ targetUserId, targetTenantId });
Defensive patterns

Strategy: validation

Validate before calling

const isRoot = myTenantId === rootTenantId;
if (!isRoot && targetTenantId !== myTenantId) {
  throw new Error('cross-tenant impersonation requires a platform operator (root tenant) account');
}

Try / catch

try { await api.startImpersonation(req); }
catch (e) { if (e.status === 403) { notify('Only platform operators may impersonate across tenants'); return; } throw e; }

Prevention

When it happens

Trigger: A tenant admin of tenant A calls start-impersonation with TargetTenantId = tenant B while the actor's tenant is neither root nor B.

Common situations: Support staff trying to help a user in another tenant without platform-operator credentials; tests using a regular tenant token expecting cross-tenant access; tenant id case/whitespace mismatch making the equality check fail unintentionally.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/937b6344e33047a0. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/StartImpersonation/StartImpersonationCommandHandler.cs:69

    {
        ArgumentNullException.ThrowIfNull(request);

        if (!_currentUser.IsAuthenticated())
        {
            throw new UnauthorizedException();
        }

        var actorUserId = _currentUser.GetUserId().ToString();
        var actorTenantId = _currentUser.GetTenant()
            ?? throw new UnauthorizedException("missing tenant context");
        var actorUserName = _currentUser.Name;

        // Cross-tenant impersonation requires the actor to be in the root tenant. Tenant admins
        // can only impersonate users within their own tenant.
        if (!string.Equals(actorTenantId, MultitenancyConstants.Root.Id, StringComparison.Ordinal)
            && !string.Equals(actorTenantId, request.TargetTenantId, StringComparison.Ordinal))
        {
            throw new ForbiddenException("cross-tenant impersonation is restricted to platform operators");
        }

        // Prevent self-impersonation (pointless, confuses the audit trail). Caller error → explicit 4xx,
        // not the 500 CustomException defaults to.
        if (string.Equals(actorUserId, request.TargetUserId, StringComparison.Ordinal)
            && string.Equals(actorTenantId, request.TargetTenantId, StringComparison.Ordinal))
        {
            throw new CustomException("cannot impersonate yourself", errors: null, System.Net.HttpStatusCode.BadRequest);
        }

        // Prevent nesting: if the caller is already impersonating, require end-impersonation first.
        var callerClaims = _currentUser.GetUserClaims();
        if (callerClaims is not null
            && callerClaims.Any(c => c.Type == ClaimConstants.ActorSubject))
        {
            throw new CustomException(
                "end current impersonation before starting a new one",
                errors: null,

View on GitHub (pinned to 3f2959e683)