fullstackhero/dotnet-starter-kit · error · UnauthorizedException
Unauthorized
Error message
Unauthorized
What it means
EndImpersonationCommandHandler.Handle first checks _currentUser.IsAuthenticated() and throws UnauthorizedException when the caller has no valid principal. Ending impersonation requires an authenticated user because the handler must read the actor claims (act_sub, act_tenant) from the current token to mint the original actor's new token.
Solutions
- Re-authenticate to obtain a fresh token (if possible via the original login flow) before calling EndImpersonation.
- Ensure the Authorization: Bearer header is actually sent on the EndImpersonation request.
- If tokens expire quickly, end impersonation proactively before expiry or handle 401 by falling back to a fresh login.
- Check JWT validation settings (issuer/audience/signing key) if valid tokens are being rejected.
Example fix
// before
await api.PostAsync("/impersonation/end"); // 401: token expired
// after
if (auth.isTokenExpired()) {
await auth.loginAgain(); // cannot silently end impersonation without a valid session
return;
}
await api.post("/impersonation/end"); Defensive patterns
Strategy: try-catch
Validate before calling
if (!auth.isAuthenticated() || auth.isTokenExpired()) {
await auth.refreshOrLogin();
return; // only call EndImpersonation with a live session
} Type guard
bool canEndImpersonation(AuthState s) => s is { IsAuthenticated: true, AccessToken.Length: > 0 }; Try / catch
try
{
await api.post("/impersonation/end");
}
catch (UnauthorizedException)
{
await auth.login(); // session gone; recover via fresh login
} Prevention
- Attach the Authorization header via a central HTTP interceptor.
- Refresh tokens proactively before they expire during long impersonation sessions.
- Treat any 401 as 'go to login', not as an application bug.
When it happens
Trigger: Calling the EndImpersonation endpoint with no JWT, an expired token, or an otherwise invalid/unrecognized authentication scheme so ICurrentUser.IsAuthenticated() returns false.
Common situations: The impersonation/access token expired while impersonating; the client never attached the Authorization header; the token was issued before a key/signing-credential rotation and now fails validation.
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/d05f1d9bf5f6d89d.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/EndImpersonation/EndImpersonationCommandHandler.cs:52
{
_identityService = identityService;
_tokenService = tokenService;
_securityAudit = securityAudit;
_currentUser = currentUser;
_requestContext = requestContext;
_grantService = grantService;
_logger = logger;
}
public async ValueTask<TokenResponse> Handle(
EndImpersonationCommand request,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
if (!_currentUser.IsAuthenticated())
{
throw new UnauthorizedException();
}
var claims = _currentUser.GetUserClaims()?.ToList()
?? throw new UnauthorizedException();
var actorUserId = claims.FirstOrDefault(c => c.Type == ClaimConstants.ActorSubject)?.Value;
var actorTenantId = claims.FirstOrDefault(c => c.Type == ClaimConstants.ActorTenant)?.Value;
var jti = claims.FirstOrDefault(c => c.Type == JwtRegisteredClaimNames.Jti)?.Value;
if (string.IsNullOrWhiteSpace(actorUserId) || string.IsNullOrWhiteSpace(actorTenantId))
{
// Signed in but no act_sub claim (End called on a non-impersonation token): client error,
// must be 4xx not CustomException's default 500.
throw new CustomException(
"current session is not an impersonation session",
errors: null,
System.Net.HttpStatusCode.BadRequest);
}View on GitHub (pinned to 3f2959e683)