{"record":{"id":"a98440351384b5e6","repo":"fullstackhero/dotnet-starter-kit","slug":"no-current-user-listmychannelsqueryhandler","errorCode":null,"errorMessage":"no current user","messagePattern":"no current user","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Chat/Modules.Chat/Features/v1/Channels/ListMyChannels/ListMyChannelsQueryHandler.cs","lineNumber":22,"sourceCode":"using FSH.Modules.Chat.Contracts.v1.DTOs;\nusing FSH.Modules.Chat.Contracts.v1.Queries;\nusing FSH.Modules.Chat.Data;\nusing FSH.Modules.Chat.Features.v1.Internal;\nusing Mediator;\nusing Microsoft.EntityFrameworkCore;\n\nnamespace FSH.Modules.Chat.Features.v1.Channels.ListMyChannels;\n\npublic sealed class ListMyChannelsQueryHandler(\n    ChatDbContext db,\n    ICurrentUser currentUser)\n    : IQueryHandler<ListMyChannelsQuery, ReadOnlyCollection<ChannelDto>>\n{\n    public async ValueTask<ReadOnlyCollection<ChannelDto>> Handle(ListMyChannelsQuery q, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(q);\n        var userId = currentUser.GetUserId();\n        if (userId == Guid.Empty) throw new UnauthorizedException(\"no current user\");\n        var currentUserId = userId.ToString();\n\n        int page = Math.Max(1, q.Page);\n        int pageSize = Math.Clamp(q.PageSize, 1, 200);\n\n        var channels = await db.Channels.AsNoTracking()\n            .Where(c => c.Members.Any(m => m.UserId == currentUserId))\n            .OrderByDescending(c => c.LastMessageAtUtc ?? c.CreatedAtUtc)\n            .Skip((page - 1) * pageSize)\n            .Take(pageSize)\n            .ToListAsync(cancellationToken)\n            .ConfigureAwait(false);\n\n        // Single round-trip: count each channel's unread messages via a correlated subquery instead of\n        // one CountAsync per channel (was N+1, up to 200 round-trips).\n        var channelIds = channels.Select(c => c.Id).ToList();\n        var unread = await db.Channels.AsNoTracking()\n            .Where(c => channelIds.Contains(c.Id))","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Chat/Modules.Chat/Features/v1/Channels/ListMyChannels/ListMyChannelsQueryHandler.cs#L4-L40","documentation":"ListMyChannelsQueryHandler requires an authenticated user; when currentUser.GetUserId() returns Guid.Empty (no valid identity on the request), it throws UnauthorizedException before querying. This is an auth-scope error, not a data error.","triggerScenarios":"Calling GET /channels/my (paginated list of my channels) without a valid JWT, with an expired token, or in any context where the current-user accessor cannot resolve a user id.","commonSituations":"App boots and fetches channels before auth bootstrap completes; token refresh failed silently; Authorization header dropped by an intercepting proxy; testing the endpoint with curl without a token.","solutions":["Gate the initial channels fetch behind completed authentication (await token readiness).","Refresh the access token and retry the request.","Inspect the JWT claims and the host's bearer/token validation settings.","Confirm the Authorization header survives any reverse proxy."],"exampleFix":"// before\nuseQuery({ queryKey: ['channels'], queryFn: () => api.get('/channels/my') });\n// after\nuseQuery({\n  queryKey: ['channels'],\n  queryFn: () => api.get('/channels/my'),\n  enabled: isAuthenticated\n});","handlingStrategy":"try-catch","validationCode":"if (!accessToken) throw new Error('Not authenticated');","typeGuard":null,"tryCatchPattern":"try { ... } catch (e) {\n  if (isUnauthorized(e)) { redirectToLogin(); }\n  else throw e;\n}","preventionTips":["Gate the channels query behind an isAuthenticated flag (TanStack Query 'enabled').","Wait for auth bootstrap before firing initial data loads.","Handle 401 globally with token refresh + replay.","Test endpoints with a fresh token; expired tokens fail even for valid users."],"tags":["auth","jwt","unauthorized"],"backgroundTag":"authentication-required","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}