{"record":{"id":"1b55cd8863b972a6","repo":"fullstackhero/dotnet-starter-kit","slug":"unauthorized-startimpersonationcommandhandler","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/StartImpersonation/StartImpersonationCommandHandler.cs","lineNumber":56,"sourceCode":"        _identityService = identityService;\n        _tokenService = tokenService;\n        _securityAudit = securityAudit;\n        _currentUser = currentUser;\n        _requestContext = requestContext;\n        _grantService = grantService;\n        _timeProvider = timeProvider;\n        _logger = logger;\n    }\n\n    public async ValueTask<ImpersonationResponse> Handle(\n        StartImpersonationCommand request,\n        CancellationToken cancellationToken)\n    {\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)","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/StartImpersonation/StartImpersonationCommandHandler.cs#L38-L74","documentation":"StartImpersonationCommandHandler.Handle throws UnauthorizedException with no message when _currentUser.IsAuthenticated() is false. Impersonation requires an authenticated actor; the exception maps to HTTP 401.","triggerScenarios":"Calling the start-impersonation endpoint without a JWT, with an expired token, or with a malformed Authorization header.","commonSituations":"Access token expired between page load and the request; missing Authorization header in a service-to-service call; API hit before login flow completed in dev tools/Playwright tests.","solutions":["Authenticate and obtain a fresh JWT before calling the endpoint","Attach the Authorization: Bearer header to the request","Refresh the token if expired, then retry"],"exampleFix":"// before\nawait fetch('/api/v1/impersonation/start', { method: 'POST' });\n// after\nawait fetch('/api/v1/impersonation/start', { method: 'POST', headers: { Authorization: `Bearer ${token}` } });","handlingStrategy":"validation","validationCode":"if (!token || isTokenExpired(token)) throw new Error('authenticate before starting impersonation');","typeGuard":null,"tryCatchPattern":"try { await api.startImpersonation(req); }\ncatch (e) { if (e.status === 401) { await auth.login(); return retryOnce(); } throw e; }","preventionTips":["Attach the Authorization header in a shared apiFetch wrapper","Refresh tokens proactively before they expire","In tests, perform login in a setup hook before impersonation calls"],"tags":["auth","jwt","impersonation"],"backgroundTag":"authentication-required","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"}