fullstackhero/dotnet-starter-kit · error · NotFoundException

impersonation grant not found

Error message

impersonation grant not found

What it means

RevokeAsync in ImpersonationGrantService throws NotFoundException when no ImpersonationGrants row matches the supplied id. The lookup is tenant-scoped via the DbContext; a nonexistent or already-purged grant id produces this error.

Solutions

  1. Verify the grant id exists (query ImpersonationGrants by id) before revoking
  2. Confirm you are pointed at the correct environment/database
  3. Re-fetch the grants list — the grant may have been purged
  4. Handle NotFoundException in the client and surface 'grant not found' to the user

Example fix

var grant = await db.ImpersonationGrants.FirstOrDefaultAsync(g => g.Id == id, ct);
if (grant is null) return Results.NotFound($"grant {id} not found");
await service.RevokeAsync(id, userId, ct);
Defensive patterns

Strategy: try-catch

Validate before calling

var exists = await db.ImpersonationGrants.AnyAsync(g => g.Id == grantId, ct);
if (!exists) return Results.NotFound($"grant {grantId} not found");

Type guard

bool IsValidGrant(object? grant) => grant is ImpersonationGrant { Id: not Guid.Empty };

Try / catch

try
{
    await impersonationService.RevokeAsync(grantId, adminUserId, ct);
}
catch (NotFoundException)
{
    return Results.NotFound("impersonation grant not found");
}

Prevention

When it happens

Trigger: Calling RevokeAsync(id, revokedByUserId, ...) with an id that does not exist in the ImpersonationGrants table (typo, wrong environment/DB, or grant deleted by cleanup).

Common situations: Client cached a stale grant id after data re-seed; calling revoke against the wrong database/environment; grant purged by a retention job between listing and revoking.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/188da5044f1fd995. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Services/ImpersonationGrantService.cs:85

        await db.SaveChangesAsync(ct).ConfigureAwait(false);
        await SetCachedStatusAsync(grant, GrantState.EndedOrRevoked, ct).ConfigureAwait(false);

        return ToDto(grant);
    }

    public async Task<ImpersonationGrantDto> RevokeAsync(
        Guid id,
        string revokedByUserId,
        string? revokedByUserName,
        string? reason,
        CancellationToken ct = default)
    {
        ArgumentNullException.ThrowIfNullOrWhiteSpace(revokedByUserId);

        var grant = await db.ImpersonationGrants
            .FirstOrDefaultAsync(g => g.Id == id, ct)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("impersonation grant not found");

        if (grant.IsTerminal)
        {
            // Idempotent — surface the existing terminal state to the caller.
            return ToDto(grant);
        }

        grant.Revoke(
            revokedAtUtc: timeProvider.GetUtcNow().UtcDateTime,
            revokedByUserId: revokedByUserId,
            revokedByUserName: revokedByUserName,
            reason: reason);

        await db.SaveChangesAsync(ct).ConfigureAwait(false);
        // Write the revocation marker BEFORE returning so a racing request
        // doesn't slip through with a cached Active marker.
        await SetCachedStatusAsync(grant, GrantState.EndedOrRevoked, ct).ConfigureAwait(false);

View on GitHub (pinned to 3f2959e683)