fullstackhero/dotnet-starter-kit · error · UnauthorizedAccessException

Cannot revoke sessions for another user

Error message

Cannot revoke sessions for another user

What it means

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.

Solutions

  1. Derive userId from the current authenticated principal, not from client input
  2. If bulk revoking for another user is required, use the admin path with the correct permission
  3. Re-authenticate as the target user if the intent is to revoke that account's sessions

Example fix

// before
await sessionService.RevokeAllSessionsAsync(requestedUserId, null, ct);
// after
var currentUserId = currentUser.GetUserId().ToString();
await sessionService.RevokeAllSessionsAsync(currentUserId, null, ct);
Defensive patterns

Strategy: validation

Validate before calling

var currentUserId = currentUser.GetUserId().ToString();
if (!string.Equals(userId, currentUserId, StringComparison.OrdinalIgnoreCase))
    return Results.Forbid();

Try / catch

try
{
    await sessionService.RevokeAllSessionsAsync(userId, exceptSessionId, ct);
}
catch (UnauthorizedAccessException)
{
    return Results.Forbid();
}

Prevention

When it happens

Trigger: Calling RevokeAllSessionsAsync(otherUserId, exceptSessionId, ...) while authenticated as someone else, commonly after passing a stale or client-controlled userId.

Common situations: Frontend stored the userId from a previous login; shared device where accounts were switched; calling the endpoint with a userId taken from URL parameters.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/e888d31039efa9a0. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Identity/Modules.Identity/Services/SessionService.cs:227

            _logger.LogInformation("Session {SessionId} revoked by {RevokedBy}", sessionId, revokedBy);
        }

        return true;
    }

    public async Task<int> RevokeAllSessionsAsync(
        string userId,
        string revokedBy,
        Guid? exceptSessionId = null,
        string? reason = null,
        CancellationToken cancellationToken = default)
    {
        EnsureValidTenant();

        var currentUserId = _currentUser.GetUserId().ToString();
        if (!string.Equals(userId, currentUserId, StringComparison.OrdinalIgnoreCase))
        {
            throw new UnauthorizedAccessException("Cannot revoke sessions for another user");
        }

        var query = _db.UserSessions
            .Where(s => s.UserId == userId && !s.IsRevoked);

        if (exceptSessionId.HasValue)
        {
            query = query.Where(s => s.Id != exceptSessionId.Value);
        }

        var sessions = await query.ToListAsync(cancellationToken);

        var tenantId = _multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id;
        foreach (var session in sessions)
        {
            session.Revoke(revokedBy, reason ?? "User requested logout from all devices", tenantId);
        }

View on GitHub (pinned to 3f2959e683)