fullstackhero/dotnet-starter-kit · error · CustomException
Cannot post a comment without an authenticated author.
Error message
Cannot post a comment without an authenticated author.
What it means
AddTicketCommentCommandHandler resolves the current user via currentUser.GetUserId(); if it returns Guid.Empty (no authenticated principal), the handler throws a 401 CustomException because a comment cannot be attributed to an author. The guard protects comment auditability.
Solutions
- Ensure the request carries a valid Bearer token before calling the add-comment endpoint.
- Refresh an expired JWT and retry the request.
- Verify the endpoint requires authorization ([Authorize] / permission gate) so anonymous calls are rejected earlier.
- In tests, configure ICurrentUser.GetUserId() to return a non-empty Guid.
Example fix
// before
await http.PostAsync($"/tickets/v1/{id}/comments", content); // no auth header
// after
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
await http.SendAsync(request); Defensive patterns
Strategy: validation
Validate before calling
if (currentUser.GetUserId() == Guid.Empty)
throw new UnauthorizedAccessException("Sign in before posting a comment."); Type guard
static bool IsAuthenticated(ICurrentUser user) => user.GetUserId() != Guid.Empty;
Try / catch
try { await mediator.Send(new AddTicketCommentCommand { TicketId = id, Body = body }); }
catch (CustomException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { await refreshTokenAndRetry(); } Prevention
- Always attach the Bearer token via an API-client interceptor.
- Handle 401 globally by refreshing the token once and replaying the request.
- Require authorization on comment endpoints so anonymous calls fail at the gate.
- In tests, stub ICurrentUser with a real user id.
When it happens
Trigger: POSTing a ticket comment with no/invalid JWT, an expired token stripped by auth middleware, or an anonymous endpoint invocation where ICurrentUser.GetUserId() yields Guid.Empty.
Common situations: Calling the API without the Authorization header; token expired client-side but request still routed; integration tests invoking the handler without a mocked ICurrentUser returning a real user id.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Cannot create a ticket without an authenticated reporter.
- no current user
- no current user
- no current user
- no current user
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/6a85ae45bdacf0b9.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Tickets/Modules.Tickets/Features/v1/Tickets/AddTicketComment/AddTicketCommentCommandHandler.cs:23
using FSH.Modules.Tickets.Data;
using Mediator;
using Microsoft.EntityFrameworkCore;
namespace FSH.Modules.Tickets.Features.v1.Tickets.AddTicketComment;
public sealed class AddTicketCommentCommandHandler(
TicketsDbContext dbContext,
ICurrentUser currentUser)
: ICommandHandler<AddTicketCommentCommand, Guid>
{
public async ValueTask<Guid> Handle(AddTicketCommentCommand command, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
var authorId = currentUser.GetUserId();
if (authorId == Guid.Empty)
{
throw new CustomException(
"Cannot post a comment without an authenticated author.",
(IEnumerable<string>?)null,
HttpStatusCode.Unauthorized);
}
// Load the Comments collection up front so EF's change tracker detects the new TicketComment
// (added via the aggregate) as an INSERT rather than missing it during change detection.
var ticket = await dbContext.Tickets
.Include(t => t.Comments)
.FirstOrDefaultAsync(t => t.Id == command.TicketId, cancellationToken)
.ConfigureAwait(false)
?? throw new NotFoundException($"Ticket {command.TicketId} not found.");
var commentId = ticket.AddComment(authorId, command.Body);
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return commentId;
}
}View on GitHub (pinned to 3f2959e683)