fullstackhero/dotnet-starter-kit · error · UnauthorizedException

no current user

Error message

no current user

What it means

GetChannelByIdQueryHandler requires an authenticated caller; currentUser.GetUserId() returning Guid.Empty means no user was resolved from the JWT/context. The handler throws UnauthorizedException before doing any data access, so the request never reaches the channel lookup.

Solutions

  1. Refresh or re-acquire the JWT and retry with a valid Authorization: Bearer header.
  2. Verify the auth middleware/JWT config (issuer, audience, signing key) on the host.
  3. Ensure the gateway/proxy forwards the Authorization header.
  4. Confirm the user claim mapping (nameidentifier) is populated in the token.

Example fix

// before
fetch(`${base}/channels/${id}`);
// after
const res = await fetch(`${base}/channels/${id}`, {
  headers: { Authorization: `Bearer ${await getValidAccessToken()}` }
});
if (res.status === 401) await reauthenticate();
Defensive patterns

Strategy: try-catch

Validate before calling

if (!accessToken || isTokenExpired(accessToken)) throw new Error('Not authenticated');

Type guard

const isAuthenticated = (u: unknown): u is { id: string } =>
  !!u && typeof (u as any).id === 'string' && (u as any).id.length > 0;

Try / catch

try { ... } catch (e) {
  if (isUnauthorized(e)) { await reauthenticate(); retry(); }
  else throw e;
}

Prevention

When it happens

Trigger: Calling GET /channels/{channelId} with a missing, malformed, or expired JWT Bearer token, or from an anonymous context where ICurrentUser.GetUserId() yields Guid.Empty.

Common situations: Token expired client-side but request still fired; Authorization header stripped by a proxy/gateway; hitting the endpoint without the [Authorize]-effective auth scheme during local testing; clock skew invalidating the JWT.

Related errors


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

Appendix: source

Thrown at src/Modules/Chat/Modules.Chat/Features/v1/Channels/GetChannelById/GetChannelByIdQueryHandler.cs:21

using FSH.Modules.Chat.Contracts.v1.DTOs;
using FSH.Modules.Chat.Contracts.v1.Queries;
using FSH.Modules.Chat.Data;
using FSH.Modules.Chat.Features.v1.Internal;
using Mediator;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Chat.Features.v1.Channels.GetChannelById;

public sealed class GetChannelByIdQueryHandler(
    ChatDbContext db,
    ICurrentUser currentUser)
    : IQueryHandler<GetChannelByIdQuery, ChannelDto>
{
    public async ValueTask<ChannelDto> Handle(GetChannelByIdQuery q, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(q);
        var userId = currentUser.GetUserId();
        if (userId == Guid.Empty) throw new UnauthorizedException("no current user");
        var currentUserId = userId.ToString();

        var channel = await db.Channels.AsNoTracking()
            .FirstOrDefaultAsync(c => c.Id == q.ChannelId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("Channel not found.");

        // Private channels & DMs: must be a member. Public channels: anyone with View can see them.
        if (channel.IsPrivate)
        {
            channel.RequireMember(currentUserId);
        }

        var member = channel.Members.FirstOrDefault(m => string.Equals(m.UserId, currentUserId, StringComparison.Ordinal));
        int unread = 0;
        if (member is not null)
        {
            unread = await db.Messages.AsNoTracking()

View on GitHub (pinned to 3f2959e683)