fullstackhero/dotnet-starter-kit · error · UnauthorizedException

no current user

Error message

no current user

What it means

EditMessageCommandHandler throws UnauthorizedException("no current user") when currentUser.GetUserId() yields Guid.Empty — no authenticated identity on the request. It guards the top of Handle before any lookup. Results in HTTP 401 via the shared exception middleware.

Solutions

  1. Attach a valid bearer token to the edit request; refresh expired tokens.
  2. Verify [Authorize] and UseAuthentication/UseAuthorization are configured for the endpoint.
  3. Ensure auth state is ready in the UI before enabling message editing.
  4. In tests, mock ICurrentUser.GetUserId() to return a non-empty Guid.

Example fix

// before
var userId = currentUser.GetUserId(); // Guid.Empty in test

// after
currentUserMock.GetCurrentUserId().Returns(Guid.NewGuid()); // authenticated test context
Defensive patterns

Strategy: try-catch

Validate before calling

if (!auth.user?.id) return; // no identity — don't attempt the edit

Type guard

function hasUserId(u: unknown): u is { id: string } {
  return typeof u === "object" && u !== null && typeof (u as { id?: unknown }).id === "string"
    && (u as { id: string }).id !== "00000000-0000-0000-0000-000000000000";
}

Try / catch

try {
  await api.editMessage(messageId, body);
} catch (e) {
  if (e.status === 401) { await reauth(); }
  else throw e;
}

Prevention

When it happens

Trigger: PATCH/PUT edit-message call with missing/expired JWT; handler invoked from a job or test without an authenticated ICurrentUser; SignalR-triggered edit outside an HTTP auth context.

Common situations: Frontend fired the edit before login completed or after token expiry; endpoint missing [Authorize]; tests using a bare handler with default mocks (Guid.Empty).

Related errors


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

Appendix: source

Thrown at src/Modules/Chat/Modules.Chat/Features/v1/Messages/EditMessage/EditMessageCommandHandler.cs:23

using FSH.Modules.Chat.Data;
using FSH.Modules.Chat.Features.v1.Internal;
using Mediator;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Chat.Features.v1.Messages.EditMessage;

public sealed class EditMessageCommandHandler(
    ChatDbContext db,
    ICurrentUser currentUser,
    IHubContext<AppHub> hub)
    : ICommandHandler<EditMessageCommand, Unit>
{
    public async ValueTask<Unit> Handle(EditMessageCommand cmd, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(cmd);
        var userId = currentUser.GetUserId();
        if (userId == Guid.Empty) throw new UnauthorizedException("no current user");
        var currentUserId = userId.ToString();

        var message = await db.Messages.FirstOrDefaultAsync(m => m.Id == cmd.MessageId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("Message not found.");

        // Verify membership through the parent channel (don't leak existence to non-members).
        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == message.ChannelId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("Message not found.");
        channel.RequireMember(currentUserId);

        message.Edit(cmd.Body, currentUserId); // domain enforces author-only
        await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);

        await hub.Clients.Group($"channel:{channel.Id}")
            .SendAsync("ChatMessageEdited", message.ToDto(), cancellationToken)
            .ConfigureAwait(false);

View on GitHub (pinned to 3f2959e683)