{"record":{"id":"8b7b8e8409d4d75d","repo":"fullstackhero/dotnet-starter-kit","slug":"no-current-user-requestuploadurlcommandhandler","errorCode":null,"errorMessage":"no current user","messagePattern":"no current user","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs","lineNumber":35,"sourceCode":"\npublic sealed class RequestUploadUrlCommandHandler(\n    FilesDbContext db,\n    IStorageService storage,\n    FileAccessPolicyRegistry policies,\n    IQuotaService quotas,\n    ICurrentUser currentUser,\n    IOptions<FilesOptions> options)\n    : ICommandHandler<RequestUploadUrlCommand, PresignedUploadResponse>\n{\n    public async ValueTask<PresignedUploadResponse> Handle(RequestUploadUrlCommand cmd, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(cmd);\n\n        var tenantId = currentUser.GetTenant() ?? throw new UnauthorizedException(\"invalid tenant\");\n        var userId = currentUser.GetUserId();\n        if (userId == Guid.Empty)\n        {\n            throw new UnauthorizedException(\"no current user\");\n        }\n\n        // Category lookup + extension/size validation.\n        if (!options.Value.Categories.TryGetValue(cmd.Category, out var category))\n        {\n            throw new CustomException($\"Unknown category '{cmd.Category}'.\", (IEnumerable<string>?)null, HttpStatusCode.BadRequest);\n        }\n\n        var extension = Path.GetExtension(cmd.FileName);\n        if (string.IsNullOrWhiteSpace(extension) ||\n            !category.AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))\n        {\n            throw new CustomException(\n                $\"Extension '{extension}' not allowed for category '{cmd.Category}'.\",\n                (IEnumerable<string>?)null,\n                HttpStatusCode.BadRequest);\n        }\n","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs#L17-L53","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","solutions":["Attach a valid, non-expired Bearer access token to the request and retry.","Decode the JWT and confirm the user identifier claim (sub/nameidentifier) exists and maps correctly to currentUser.GetUserId().","Fix token issuance if the identifier claim is missing; check claim-type mapping (JwtSecurityTokenHandler DefaultInboundClaimTypeMap / MapInboundClaims) in ConfigureJwtBearerOptions.","Ensure the endpoint is not AllowAnonymous and authentication middleware runs before the handler."],"exampleFix":"// before\nawait apiFetch(\"/files/v1/request-upload-url\", { method: \"POST\" }); // no Authorization header\n// after\nawait apiFetch(\"/files/v1/request-upload-url\", { method: \"POST\", headers: { Authorization: `Bearer ${token}` } });","handlingStrategy":"validation","validationCode":"if (!token || isExpired(token)) await refreshSession();\nconst userId = parseJwt(token).sub;\nif (!userId) throw new Error(\"Token has no user identifier claim\");","typeGuard":"function hasUserSubject(claims) { return typeof claims.sub === \"string\" && claims.sub.length > 0; }","tryCatchPattern":"try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 401 && e.message === \"no current user\") { await relogin(); retry(); } else throw e; }","preventionTips":["Attach the Authorization header centrally in the API client (apiFetch) so it can never be omitted.","Handle 401 globally by refreshing/expiring the session rather than letting requests proceed.","After changing claim mappings, force users to re-login to pick up corrected tokens."],"tags":["authentication","jwt","files"],"backgroundTag":"authentication-required","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}