fullstackhero/dotnet-starter-kit · error · UnauthorizedException

no current user

Error message

no current user

What it means

DeleteMessageCommandHandler throws UnauthorizedException("no current user") when currentUser.GetUserId() returns Guid.Empty, i.e. the request carries no authenticated user identity. The guard runs before any database access so deletes are never attempted anonymously. Rendered as HTTP 401 by the module's exception handling.

Solutions

  1. Send a valid, unexpired bearer token with the request.
  2. Ensure the endpoint/hub method requires authentication and auth middleware runs before handlers.
  3. Implement token refresh on 401 in the frontend apiFetch layer.
  4. In tests/background jobs, provide a mocked ICurrentUser returning a concrete Guid.

Example fix

// before
var response = await fetch(`/api/v1/messages/${id}`, { method: "DELETE" }); // 401

// after
apiFetch(`/api/v1/messages/${id}`, { method: "DELETE" }); // attaches bearer token + refreshes
Defensive patterns

Strategy: try-catch

Validate before calling

if (!auth.accessToken || isExpired(auth.accessToken)) {
  await refreshAccessToken(); // authenticate before deleting
}

Type guard

function hasIdentity(u: { id?: string }): u is { id: string } {
  return typeof u.id === "string" && u.id !== "" && u.id !== "00000000-0000-0000-0000-000000000000";
}

Try / catch

try {
  await api.deleteMessage(messageId);
} catch (e) {
  if (e.status === 401) { await refreshAndRetry(); }
  else throw e;
}

Prevention

When it happens

Trigger: Calling the delete-message endpoint without a valid JWT; invoking the handler from a Hangfire job, SignalR invocation, or test where no ICurrentUser is established.

Common situations: Expired access token not refreshed by the frontend; missing [Authorize] on the endpoint letting anonymous calls through to the handler; SignalR hub methods invoked before negotiation completes authentication; test doubles returning default(Guid).

Related errors


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

Appendix: source

Thrown at src/Modules/Chat/Modules.Chat/Features/v1/Messages/DeleteMessage/DeleteMessageCommandHandler.cs:26

using FSH.Modules.Identity.Contracts.Services;
using Mediator;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;

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

public sealed class DeleteMessageCommandHandler(
    ChatDbContext db,
    ICurrentUser currentUser,
    IUserPermissionService permissions,
    IHubContext<AppHub> hub)
    : ICommandHandler<DeleteMessageCommand, Unit>
{
    public async ValueTask<Unit> Handle(DeleteMessageCommand 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.");

        var channel = await db.Channels.FirstOrDefaultAsync(c => c.Id == message.ChannelId, cancellationToken)
            .ConfigureAwait(false)
            ?? throw new NotFoundException("Message not found.");
        channel.RequireMember(currentUserId);

        bool isModerator = await permissions
            .HasPermissionAsync(currentUserId, ChatPermissions.Messages.DeleteAny, cancellationToken)
            .ConfigureAwait(false);

        message.SoftDelete(currentUserId, isModerator);

        // If this was a thread reply, decrement the parent's ReplyCount.

View on GitHub (pinned to 3f2959e683)