fullstackhero/dotnet-starter-kit · error · CustomException

Cannot create a ticket without an authenticated reporter.

Error message

Cannot create a ticket without an authenticated reporter.

What it means

CreateTicketCommandHandler resolves the reporter via currentUser.GetUserId(); a Guid.Empty result (unauthenticated or anonymous principal) triggers a 401 CustomException because every ticket must record who reported it. The ticket number sequence (TK-n) also depends on a valid tenant/authenticated context.

Solutions

  1. Attach a valid Bearer token (or service-identity token) to the create-ticket request.
  2. Refresh expired credentials and retry.
  3. For machine callers, provision a dedicated service account and authenticate as it before creating tickets.
  4. In tests, stub ICurrentUser.GetUserId() to return a real user Guid.

Example fix

// before
var handler = new CreateTicketCommandHandler(dbContext, currentUserSubstitute); // GetUserId() => Guid.Empty
// after
var currentUser = Substitute.For<ICurrentUser>();
currentUser.GetUserId().Returns(Guid.NewGuid());
var handler = new CreateTicketCommandHandler(dbContext, currentUser);
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrWhiteSpace(token) || jwtSecurityToken == null)
    throw new InvalidOperationException("A valid token is required to create a ticket.");

Type guard

static bool CanCreateTicket(ICurrentUser user) => user.GetUserId() != Guid.Empty;

Try / catch

try { await mediator.Send(new CreateTicketCommand { ... }); }
catch (CustomException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { await signInAgain(); }

Prevention

When it happens

Trigger: Posting CreateTicketCommand without a valid JWT, with an expired token, or via an endpoint missing its authorization requirement so ICurrentUser has no user id.

Common situations: Scheduled jobs or webhooks calling the create endpoint with service credentials not configured; frontend losing the token after refresh; tests constructing the handler with a default/mock ICurrentUser returning Guid.Empty.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/Modules/Tickets/Modules.Tickets/Features/v1/Tickets/CreateTicket/CreateTicketCommandHandler.cs:26

using Mediator;
using FSH.Framework.Persistence;
using Microsoft.EntityFrameworkCore;

namespace FSH.Modules.Tickets.Features.v1.Tickets.CreateTicket;

public sealed class CreateTicketCommandHandler(
    TicketsDbContext dbContext,
    ICurrentUser currentUser)
    : ICommandHandler<CreateTicketCommand, Guid>
{
    public async ValueTask<Guid> Handle(CreateTicketCommand command, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(command);

        var reporterId = currentUser.GetUserId();
        if (reporterId == Guid.Empty)
        {
            throw new CustomException(
                "Cannot create a ticket without an authenticated reporter.",
                (IEnumerable<string>?)null,
                HttpStatusCode.Unauthorized);
        }

        // Sequential, tenant-scoped ticket numbers (TK-1, …). Count ALL rows incl. soft-deleted so a
        // deleted number isn't reused; racing writers collide on the unique index (→ 409, retryable).
        long count = await dbContext.Tickets
            .IgnoreQueryFilters([QueryFilters.SoftDelete])
            .LongCountAsync(cancellationToken)
            .ConfigureAwait(false);
        string number = $"TK-{(count + 1).ToString(CultureInfo.InvariantCulture)}";

        var ticket = Ticket.Create(
            number: number,
            title: command.Title,
            description: command.Description,
            priority: command.Priority,

View on GitHub (pinned to 3f2959e683)