fullstackhero/dotnet-starter-kit · error · UnauthorizedException
missing tenant context
Error message
missing tenant context
What it means
After confirming authentication, the start-impersonation handler requires the actor's tenant claim; _currentUser.GetTenant() returning null triggers UnauthorizedException('missing tenant context') (HTTP 401). Tenant scoping is required to enforce the cross-tenant rules that follow.
Solutions
- Re-issue the token through the standard login flow so tenant claims are included
- Verify Finbuckle multitenancy registration and the tenant claim constant still match
- Assign the calling service/user to a tenant
Example fix
// before
var claims = new List<Claim> { new(ClaimTypes.NameIdentifier, userId) };
// after
var claims = new List<Claim> { new(ClaimTypes.NameIdentifier, userId), new("tenant", tenantId) }; Defensive patterns
Strategy: validation
Validate before calling
const tenant = claims.find(c => c.type === 'tenant')?.value;
if (!tenant) throw new Error('no tenant claim on token; re-login through the standard flow'); Try / catch
try { await api.startImpersonation(req); }
catch (e) { if (e.status === 401 && /tenant context/.test(e.message)) { await reauthWithTenant(); return; } throw e; } Prevention
- Verify tokens carry the tenant claim after any auth-flow change
- Keep Finbuckle tenant resolution aligned with the claim constant used by GetTenant
- Assign service accounts to a tenant
- Add a startup/CI check that a sample token contains tenant claims
When it happens
Trigger: An authenticated token without the tenant claim is used to start impersonation — e.g. a token issued by a custom auth endpoint that skips tenant claim generation, or a request bypassing the tenant resolver.
Common situations: Manually minted dev tokens lacking tenant claims; service accounts provisioned without tenant membership; Finbuckle tenant strategy changed so the claim name no longer matches what GetTenant reads.
Related errors
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/c52587d9f77849c4.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/StartImpersonation/StartImpersonationCommandHandler.cs:61
_grantService = grantService;
_timeProvider = timeProvider;
_logger = logger;
}
public async ValueTask<ImpersonationResponse> Handle(
StartImpersonationCommand request,
CancellationToken cancellationToken)
{
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);
}
View on GitHub (pinned to 3f2959e683)