{"record":{"id":"937b6344e33047a0","repo":"fullstackhero/dotnet-starter-kit","slug":"cross-tenant-impersonation-is-restricted-to-platform","errorCode":null,"errorMessage":"cross-tenant impersonation is restricted to platform operators","messagePattern":"cross-tenant impersonation is restricted to platform operators","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/StartImpersonation/StartImpersonationCommandHandler.cs","lineNumber":69,"sourceCode":"    {\n        ArgumentNullException.ThrowIfNull(request);\n\n        if (!_currentUser.IsAuthenticated())\n        {\n            throw new UnauthorizedException();\n        }\n\n        var actorUserId = _currentUser.GetUserId().ToString();\n        var actorTenantId = _currentUser.GetTenant()\n            ?? throw new UnauthorizedException(\"missing tenant context\");\n        var actorUserName = _currentUser.Name;\n\n        // Cross-tenant impersonation requires the actor to be in the root tenant. Tenant admins\n        // can only impersonate users within their own tenant.\n        if (!string.Equals(actorTenantId, MultitenancyConstants.Root.Id, StringComparison.Ordinal)\n            && !string.Equals(actorTenantId, request.TargetTenantId, StringComparison.Ordinal))\n        {\n            throw new ForbiddenException(\"cross-tenant impersonation is restricted to platform operators\");\n        }\n\n        // Prevent self-impersonation (pointless, confuses the audit trail). Caller error → explicit 4xx,\n        // not the 500 CustomException defaults to.\n        if (string.Equals(actorUserId, request.TargetUserId, StringComparison.Ordinal)\n            && string.Equals(actorTenantId, request.TargetTenantId, StringComparison.Ordinal))\n        {\n            throw new CustomException(\"cannot impersonate yourself\", errors: null, System.Net.HttpStatusCode.BadRequest);\n        }\n\n        // Prevent nesting: if the caller is already impersonating, require end-impersonation first.\n        var callerClaims = _currentUser.GetUserClaims();\n        if (callerClaims is not null\n            && callerClaims.Any(c => c.Type == ClaimConstants.ActorSubject))\n        {\n            throw new CustomException(\n                \"end current impersonation before starting a new one\",\n                errors: null,","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/StartImpersonation/StartImpersonationCommandHandler.cs#L51-L87","documentation":"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.","triggerScenarios":"A tenant admin of tenant A calls start-impersonation with TargetTenantId = tenant B while the actor's tenant is neither root nor B.","commonSituations":"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.","solutions":["Use an account in the root tenant (platform operator) for cross-tenant impersonation","Target users within your own tenant instead","Ensure TargetTenantId exactly matches the actor's tenant (ordinal, no whitespace)"],"exampleFix":"// before\nawait api.startImpersonation({ targetUserId, targetTenantId: otherTenantId }); // 403\n// after\nconst targetTenantId = myTenantId; // stay in-tenant, or use a root-tenant operator token\nawait api.startImpersonation({ targetUserId, targetTenantId });","handlingStrategy":"validation","validationCode":"const isRoot = myTenantId === rootTenantId;\nif (!isRoot && targetTenantId !== myTenantId) {\n  throw new Error('cross-tenant impersonation requires a platform operator (root tenant) account');\n}","typeGuard":null,"tryCatchPattern":"try { await api.startImpersonation(req); }\ncatch (e) { if (e.status === 403) { notify('Only platform operators may impersonate across tenants'); return; } throw e; }","preventionTips":["Gate cross-tenant impersonation UI behind operator role checks","Normalize/trim tenant ids before comparing (API compares ordinally)","Document that tenant admins are limited to their own tenant"],"tags":["authorization","multitenancy","impersonation"],"backgroundTag":"permission-denied","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}