fullstackhero/dotnet-starter-kit · error · NotFoundException
target user not found
Error message
target user not found
What it means
The handler asks IIdentityService.BuildClaimsForUserAsync to build claims for the target user in the target tenant. A null result means no such user exists in that tenant, so it throws NotFoundException('target user not found') (HTTP 404).
Solutions
- Verify TargetUserId and TargetTenantId match an existing, active user (re-fetch the user list for that tenant)
- Confirm you are passing the user's id, not email or username
- Check the target tenant id spelling against the tenants list
Example fix
// before
await api.startImpersonation({ targetUserId: user.email, targetTenantId }); // 404
// after
await api.startImpersonation({ targetUserId: user.id, targetTenantId }); Defensive patterns
Strategy: validation
Validate before calling
const user = await api.getUser(targetUserId, targetTenantId).catch(() => null);
if (!user) throw new Error(`no user ${targetUserId} in tenant ${targetTenantId}`); Try / catch
try { await api.startImpersonation(req); }
catch (e) { if (e.status === 404 && /target user/.test(e.message)) { refreshTargetUserList(); return; } throw e; } Prevention
- Resolve targets from the tenant-scoped user list, not free-text input
- Pass user ids, not emails/usernames
- Re-fetch the user when the target list is stale
- Verify the tenant id belongs to the same environment
When it happens
Trigger: TargetUserId does not exist, exists in a different tenant than TargetTenantId, or the user is inactive/deleted in that tenant.
Common situations: Target user id from another environment; targeting a user by email instead of id; tenant mismatch — user exists but under a different tenant id; user deleted while the support UI list was open.
Understand the failure class
Background: "User not found", "Invalid user", and "does not exist": what missing-user lookup errors mean across Rocket.Chat, LiteLLM, Phabricator, rustfs, and pnpm — this error's family across 10 libraries.
Related errors
- original actor not found
- impersonation grant not found
- impersonation grant not found
- Audit record not found.
- Invoice not found.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/14da1e23626fcebe.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/StartImpersonation/StartImpersonationCommandHandler.cs:96
}
// 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,
System.Net.HttpStatusCode.BadRequest);
}
var targetClaimsResult = await _identityService
.BuildClaimsForUserAsync(request.TargetUserId, request.TargetTenantId, cancellationToken);
if (targetClaimsResult is null)
{
throw new NotFoundException("target user not found");
}
var (subject, claims) = targetClaimsResult.Value;
var targetUserName = claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value
?? claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Name)?.Value;
// Strip the auto-generated jti from BuildClaimsForUserAsync and inject our own, so the persisted
// ImpersonationGrant row and the issued JWT share the same jti.
var jti = Guid.NewGuid().ToString("N");
var impersonationClaims = claims
.Where(c => c.Type != JwtRegisteredClaimNames.Jti)
.Concat(
[
new Claim(JwtRegisteredClaimNames.Jti, jti),
// RFC 8693 actor claims so the issued token carries who is acting.
new Claim(ClaimConstants.ActorSubject, actorUserId),
new Claim(ClaimConstants.ActorTenant, actorTenantId)
])View on GitHub (pinned to 3f2959e683)