{"record":{"id":"e4bc414bfb1967ae","repo":"fullstackhero/dotnet-starter-kit","slug":"session-has-been-revoked","errorCode":null,"errorMessage":"Session has been revoked.","messagePattern":"Session has been revoked\\.","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/Tokens/RefreshToken/RefreshTokenCommandHandler.cs","lineNumber":65,"sourceCode":"        // Validate refresh token and rebuild subject + claims\n        var validated = await _identityService\n            .ValidateRefreshTokenAsync(request.RefreshToken, cancellationToken);\n\n        if (validated is null)\n        {\n            await _securityAudit.TokenRevokedAsync(\"unknown\", clientId!, \"InvalidRefreshToken\", cancellationToken);\n            throw new UnauthorizedException(\"Invalid refresh token.\");\n        }\n\n        var (subject, claims) = validated.Value;\n\n        // Check if the session associated with this refresh token is still valid\n        var refreshTokenHash = Sha256Short(request.RefreshToken);\n        var isSessionValid = await _sessionService.ValidateSessionAsync(refreshTokenHash, cancellationToken);\n        if (!isSessionValid)\n        {\n            await _securityAudit.TokenRevokedAsync(subject, clientId!, \"SessionRevoked\", cancellationToken);\n            throw new UnauthorizedException(\"Session has been revoked.\");\n        }\n\n        // Optionally, cross-check the provided access token subject\n        var handler = new JwtSecurityTokenHandler();\n        JwtSecurityToken? parsedAccessToken = null;\n        try\n        {\n            parsedAccessToken = handler.ReadJwtToken(request.Token);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogDebug(ex, \"Failed to parse access token during refresh; relying on refresh-token validation only\");\n        }\n\n        if (parsedAccessToken is not null)\n        {\n            var accessTokenSubject = parsedAccessToken.Claims\n                .FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/Tokens/RefreshToken/RefreshTokenCommandHandler.cs#L47-L83","documentation":"Handle throws UnauthorizedException(\"Session has been revoked.\") when the refresh token itself validates but the associated session fails _sessionService.ValidateSessionAsync (hashed token no longer maps to a live session).","triggerScenarios":"User logged out / admin killed the session; session expired server-side while the client still holds a valid refresh token; session store (DB/Redis) entry purged.","commonSituations":"Password change or 'sign out all devices' invalidating sessions; long-idle client with revoked session; stale cached client retrying after revocation.","solutions":["Send the user through fresh login to create a new session","Don't cache the refresh token beyond the session lifetime","Check session-store retention/TTL configuration if sessions die too early"],"exampleFix":"// before\ncatch (e) { console.error(e); }\n// after\ncatch (e) {\n  if (e.message === 'Session has been revoked.') logoutAndRedirectToLogin();\n  else throw e;\n}","handlingStrategy":"fallback","validationCode":"// nothing callable pre-API; ensure you only refresh while the user is marked 'signed in' client-side\nif (!store.sessionActive) return; // don't refresh a session you already know is gone","typeGuard":null,"tryCatchPattern":"try { return await refreshAsync(token); }\ncatch (UnauthorizedException e) when (e.Message.Contains(\"revoked\")) { await logout(); redirectToLogin(\"session-expired\"); return null; }","preventionTips":["Treat session revocation as final — do not retry the refresh","Clear local auth state as soon as logout/password-change happens","Monitor session TTL configuration so clients don't outlive their sessions"],"tags":["auth","session","revocation","unauthorized"],"backgroundTag":"session-revoked","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}