fullstackhero/dotnet-starter-kit · warning · CustomException
current session is not an impersonation session
Error message
current session is not an impersonation session
What it means
EndImpersonation requires actor claims (act_sub/act_tenant) present only on tokens minted by StartImpersonation. If the authenticated caller's token lacks them, the handler throws CustomException with HTTP 400 BadRequest — the session is valid but it is not an impersonation session, so there is nothing to end.
Solutions
- Only call EndImpersonation when the current session actually started via StartImpersonation — track that state client-side.
- Treat HTTP 400 from this endpoint as 'already a normal session' and simply continue with the current token instead of surfacing an error.
- After a successful EndImpersonation, immediately swap the stored token for the returned original-actor token and disable the end-impersonation action.
Example fix
// before
onMount(() => api.post("/impersonation/end")); // fires even for normal sessions
// after
if (session.isImpersonating) {
await api.post("/impersonation/end");
session.isImpersonating = false;
} Defensive patterns
Strategy: fallback
Validate before calling
if (!session.isImpersonating) return; // nothing to end; skip the call entirely
Type guard
bool isImpersonationToken(string jwt) =>
new JwtSecurityTokenHandler().ReadJwtToken(jwt).Claims.Any(c => c.Type == "act_sub"); Try / catch
try
{
await api.post("/impersonation/end");
}
catch (CustomException ex) when (ex.StatusCode == HttpStatusCode.BadRequest)
{
// already a normal session: keep current token, clear client impersonation flag
session.isImpersonating = false;
} Prevention
- Track impersonation state client-side and only show/call End when active.
- After ending impersonation, immediately swap the token and set the flag false.
- Ignore (don't rethrow) 400 from EndImpersonation — it means the session was already normal.
When it happens
Trigger: Calling EndImpersonation with a regular (non-impersonation) login token — one with no ClaimConstants.ActorSubject/ActorTenant claims, so actorUserId/actorTenantId come back null or whitespace.
Common situations: Double-clicking an 'exit impersonation' button after the first call already restored the original token; a UI bug that calls End on page load with the normal session; replaying a captured End request after impersonation already ended.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- cannot impersonate yourself
- end current impersonation before starting a new one
- A category cannot be its own parent.
- Setting this parent would create a cycle.
- Cannot DM yourself.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/148531a765aebd37.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/Impersonation/EndImpersonation/EndImpersonationCommandHandler.cs:66
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);
}
var impersonatedUserId = _currentUser.GetUserId().ToString();
var impersonatedTenantId = _currentUser.GetTenant() ?? string.Empty;
// Mark grant ended BEFORE issuing actor tokens so a racing JWT-hook request sees "ended" (safer than the reverse).
// If MarkEnded fails we proceed anyway: the grant expires naturally and the hook treats Unknown states as revoked.
if (!string.IsNullOrWhiteSpace(jti))
{
try
{
await _grantService.MarkEndedByJtiAsync(jti, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{View on GitHub (pinned to 3f2959e683)