fullstackhero/dotnet-starter-kit · error · UnauthorizedException

no current user

Error message

no current user

What it means

UnauthorizedException('no current user') is thrown when currentUser.GetUserId() returns Guid.Empty, meaning the request reached the handler without a resolvable authenticated user id, even though a tenant was present. The handler requires both a tenant and a concrete user to attribute and authorize the upload.

Solutions

  1. Attach a valid, non-expired Bearer access token to the request and retry.
  2. Decode the JWT and confirm the user identifier claim (sub/nameidentifier) exists and maps correctly to currentUser.GetUserId().
  3. Fix token issuance if the identifier claim is missing; check claim-type mapping (JwtSecurityTokenHandler DefaultInboundClaimTypeMap / MapInboundClaims) in ConfigureJwtBearerOptions.
  4. Ensure the endpoint is not AllowAnonymous and authentication middleware runs before the handler.

Example fix

// before
await apiFetch("/files/v1/request-upload-url", { method: "POST" }); // no Authorization header
// after
await apiFetch("/files/v1/request-upload-url", { method: "POST", headers: { Authorization: `Bearer ${token}` } });
Defensive patterns

Strategy: validation

Validate before calling

if (!token || isExpired(token)) await refreshSession();
const userId = parseJwt(token).sub;
if (!userId) throw new Error("Token has no user identifier claim");

Type guard

function hasUserSubject(claims) { return typeof claims.sub === "string" && claims.sub.length > 0; }

Try / catch

try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 401 && e.message === "no current user") { await relogin(); retry(); } else throw e; }

Prevention

When it happens

Trigger: RequestUploadUrlCommand invoked with an anonymous or anonymous-equivalent request: missing/expired/malformed JWT that authentication silently degraded, an endpoint accidentally configured with AllowAnonymous, or a token without the name/sub identifier claim the current-user service maps to GetUserId().

Common situations: Calling the endpoint without the Authorization header; expired token where the 401 challenge was bypassed (e.g. via SignalR query-string token path misconfigured); identity claims renamed or stripped by a custom token factory; integration tests constructing ICurrentUser stubs returning Guid.Empty.

Related errors


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

Appendix: source

Thrown at src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs:35

public sealed class RequestUploadUrlCommandHandler(
    FilesDbContext db,
    IStorageService storage,
    FileAccessPolicyRegistry policies,
    IQuotaService quotas,
    ICurrentUser currentUser,
    IOptions<FilesOptions> options)
    : ICommandHandler<RequestUploadUrlCommand, PresignedUploadResponse>
{
    public async ValueTask<PresignedUploadResponse> Handle(RequestUploadUrlCommand cmd, CancellationToken cancellationToken)
    {
        ArgumentNullException.ThrowIfNull(cmd);

        var tenantId = currentUser.GetTenant() ?? throw new UnauthorizedException("invalid tenant");
        var userId = currentUser.GetUserId();
        if (userId == Guid.Empty)
        {
            throw new UnauthorizedException("no current user");
        }

        // Category lookup + extension/size validation.
        if (!options.Value.Categories.TryGetValue(cmd.Category, out var category))
        {
            throw new CustomException($"Unknown category '{cmd.Category}'.", (IEnumerable<string>?)null, HttpStatusCode.BadRequest);
        }

        var extension = Path.GetExtension(cmd.FileName);
        if (string.IsNullOrWhiteSpace(extension) ||
            !category.AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
        {
            throw new CustomException(
                $"Extension '{extension}' not allowed for category '{cmd.Category}'.",
                (IEnumerable<string>?)null,
                HttpStatusCode.BadRequest);
        }

View on GitHub (pinned to 3f2959e683)