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

  1. Verify the grant id is current by re-fetching the impersonation grant list
  2. Check you are calling the correct environment/database
  3. 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

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


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)