fullstackhero/dotnet-starter-kit · error · UnauthorizedException
Unauthorized
Error message
Unauthorized
What it means
RevokeImpersonationGrantCommandHandler.Handle rejects unauthenticated callers: if currentUser.IsAuthenticated() is false it throws UnauthorizedException. Revoking an impersonation grant is a privileged operation, so the caller must present a valid authenticated principal (which is then checked for tenant context and root/tenant-admin scope).
Solutions
- Re-authenticate and retry the revoke with a fresh token.
- Ensure the HTTP client attaches the Authorization: Bearer header for this call (check interceptors).
- Handle 401 globally by redirecting to login, then re-running the pending revoke action.
- Verify JWT validation parameters (clock skew, issuer, audience, keys) if seemingly valid tokens are rejected.
Example fix
// before
await fetch(`/api/impersonation/grants/${grantId}`, { method: "DELETE" }); // no auth header
// after
await apiFetch(`/api/impersonation/grants/${grantId}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${auth.getToken()}` },
}); Defensive patterns
Strategy: try-catch
Validate before calling
if (!auth.isAuthenticated() || auth.isTokenExpired()) {
await auth.refresh(); // ensure a live token before revoking grants
return;
} Type guard
bool canRevoke(AuthState s) => s is { IsAuthenticated: true }; Try / catch
try
{
await api.delete(`/impersonation/grants/${grantId}`);
}
catch (UnauthorizedException)
{
await auth.loginThen(() => api.delete(`/impersonation/grants/${grantId}`));
} Prevention
- Use a shared API client that always injects the bearer token.
- Implement a global 401 interceptor that refreshes the token and replays the request once.
- Check token expiry before privileged admin operations instead of after failure.
When it happens
Trigger: Calling the RevokeImpersonationGrant endpoint with no token, an expired/invalid JWT, or a request missing the Authorization header entirely.
Common situations: Session expired while an admin was managing grants list; frontend sent the revoke call without attaching the bearer token; token invalidated by signing-key rotation or by a prior logout that revoked the session.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/c6cbf33f5a9997d4.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/RevokeImpersonationGrant/RevokeImpersonationGrantCommandHandler.cs:29
namespace FSH.Modules.Identity.Features.v1.Impersonation.RevokeImpersonationGrant;
public sealed class RevokeImpersonationGrantCommandHandler(
IImpersonationGrantService grantService,
ICurrentUser currentUser,
ISecurityAudit securityAudit,
IRequestContext requestContext,
ILogger<RevokeImpersonationGrantCommandHandler> logger)
: ICommandHandler<RevokeImpersonationGrantCommand, ImpersonationGrantDto>
{
public async ValueTask<ImpersonationGrantDto> Handle(
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");View on GitHub (pinned to 3f2959e683)