fullstackhero/dotnet-starter-kit · error · NotFoundException
impersonation grant not found
Error message
impersonation grant not found
What it means
The handler loads the impersonation grant by id via grantService.GetByIdAsync; when no grant with that id exists it throws NotFoundException('impersonation grant not found'), which maps to HTTP 404. This is the missing-entity case of the revoke flow.
Solutions
- Verify the grant id is current by re-fetching the impersonation grant list
- Check you are calling the correct environment/database
- Treat 404 as final for that id — do not retry
Defensive patterns
Strategy: try-catch
Validate before calling
const grants = await api.listImpersonationGrants();
if (!grants.some(g => g.id === grantId)) throw new Error(`grant ${grantId} not found; refresh list`); Try / catch
try { await api.revokeImpersonationGrant(grantId); }
catch (e) { if (e.status === 404) { refreshGrantList(); return; } throw e; } Prevention
- Re-fetch the grant list before revoking instead of caching ids
- Never reuse grant ids across environments
- Treat 404 as terminal — do not retry the revoke
When it happens
Trigger: Passing a GrantId that does not exist (already revoked and hard-deleted, wrong id, or an id from another database) to the revoke-impersonation-grant endpoint.
Common situations: Client cached a grant id from a list that was since purged; id typo or GUID from a different environment (staging vs prod); grant revoked concurrently by another admin.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Channel not found.
- original actor not found
- target user not found
- impersonation grant not found
- Audit record not found.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/bd24195c5b9b9d8b.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/RevokeImpersonationGrant/RevokeImpersonationGrantCommandHandler.cs:40
RevokeImpersonationGrantCommand request,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
if (!currentUser.IsAuthenticated())
{
throw new UnauthorizedException();
}
var callerUserId = currentUser.GetUserId().ToString();
var callerTenantId = currentUser.GetTenant()
?? throw new UnauthorizedException("missing tenant context");
var isRoot = string.Equals(callerTenantId, MultitenancyConstants.Root.Id, StringComparison.Ordinal);
// Enforce visibility before revoking: tenant admins may only revoke grants in their own
// tenant. Cross-tenant grants return 404 (not 403) so existence isn't confirmed out of scope.
var grant = await grantService.GetByIdAsync(request.GrantId, cancellationToken).ConfigureAwait(false)
?? throw new NotFoundException("impersonation grant not found");
var withinTenant = string.Equals(grant.ImpersonatedTenantId, callerTenantId, StringComparison.Ordinal)
|| string.Equals(grant.ActorTenantId, callerTenantId, StringComparison.Ordinal);
if (!isRoot && !withinTenant)
{
throw new NotFoundException("impersonation grant not found");
}
var updated = await grantService.RevokeAsync(
id: request.GrantId,
revokedByUserId: callerUserId,
revokedByUserName: currentUser.Name,
reason: request.Reason,
ct: cancellationToken).ConfigureAwait(false);
// Surface revoke as a first-class security event, queryable alongside Start/End entries.
// The audit Reason is the revocation reason, not the original impersonation reason.View on GitHub (pinned to 3f2959e683)