{"record":{"id":"e888d31039efa9a0","repo":"fullstackhero/dotnet-starter-kit","slug":"cannot-revoke-sessions-for-another-user","errorCode":null,"errorMessage":"Cannot revoke sessions for another user","messagePattern":"Cannot revoke sessions for another user","errorType":"exception","errorClass":"UnauthorizedAccessException","httpStatus":403,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Services/SessionService.cs","lineNumber":227,"sourceCode":"            _logger.LogInformation(\"Session {SessionId} revoked by {RevokedBy}\", sessionId, revokedBy);\n        }\n\n        return true;\n    }\n\n    public async Task<int> RevokeAllSessionsAsync(\n        string userId,\n        string revokedBy,\n        Guid? exceptSessionId = null,\n        string? reason = null,\n        CancellationToken cancellationToken = default)\n    {\n        EnsureValidTenant();\n\n        var currentUserId = _currentUser.GetUserId().ToString();\n        if (!string.Equals(userId, currentUserId, StringComparison.OrdinalIgnoreCase))\n        {\n            throw new UnauthorizedAccessException(\"Cannot revoke sessions for another user\");\n        }\n\n        var query = _db.UserSessions\n            .Where(s => s.UserId == userId && !s.IsRevoked);\n\n        if (exceptSessionId.HasValue)\n        {\n            query = query.Where(s => s.Id != exceptSessionId.Value);\n        }\n\n        var sessions = await query.ToListAsync(cancellationToken);\n\n        var tenantId = _multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id;\n        foreach (var session in sessions)\n        {\n            session.Revoke(revokedBy, reason ?? \"User requested logout from all devices\", tenantId);\n        }\n","sourceCodeStart":209,"sourceCodeEnd":245,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Services/SessionService.cs#L209-L245","documentation":"RevokeAllSessionsAsync throws UnauthorizedAccessException when the userId argument differs from the authenticated current user (case-insensitive). Bulk revocation is restricted to the session owner; cross-user revocation must go through an admin flow.","triggerScenarios":"Calling RevokeAllSessionsAsync(otherUserId, exceptSessionId, ...) while authenticated as someone else, commonly after passing a stale or client-controlled userId.","commonSituations":"Frontend stored the userId from a previous login; shared device where accounts were switched; calling the endpoint with a userId taken from URL parameters.","solutions":["Derive userId from the current authenticated principal, not from client input","If bulk revoking for another user is required, use the admin path with the correct permission","Re-authenticate as the target user if the intent is to revoke that account's sessions"],"exampleFix":"// before\nawait sessionService.RevokeAllSessionsAsync(requestedUserId, null, ct);\n// after\nvar currentUserId = currentUser.GetUserId().ToString();\nawait sessionService.RevokeAllSessionsAsync(currentUserId, null, ct);","handlingStrategy":"validation","validationCode":"var currentUserId = currentUser.GetUserId().ToString();\nif (!string.Equals(userId, currentUserId, StringComparison.OrdinalIgnoreCase))\n    return Results.Forbid();","typeGuard":null,"tryCatchPattern":"try\n{\n    await sessionService.RevokeAllSessionsAsync(userId, exceptSessionId, ct);\n}\ncatch (UnauthorizedAccessException)\n{\n    return Results.Forbid();\n}","preventionTips":["Pass CurrentUser.GetUserId() instead of a client-supplied userId","Clear cached user state after account switches on shared devices","Route admin-initiated bulk revocation through a permissioned admin path"],"tags":["authorization","sessions","identity"],"backgroundTag":"permission-denied","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"}