fullstackhero/dotnet-starter-kit · error · UnauthorizedException
invalid tenant
Error message
invalid tenant
What it means
UnauthorizedException('invalid tenant') is thrown by RequestUploadUrlCommandHandler.Handle when the current user's JWT has no tenant claim (currentUser.GetTenant() returns null). The Files module is tenant-scoped, so a presigned upload URL can only be issued once the request is bound to a tenant via Finbuckle multitenancy.
Solutions
- Log in again through the normal auth flow so the token includes the tenant claim, and retry the upload request.
- Send the request to the tenant-resolvable route/host or add the tenant identifier header the API's Finbuddle multitenancy strategy expects.
- Verify the token contents (decode the JWT) and confirm a tenant claim exists; if missing, fix token issuance in the identity layer.
- If the user genuinely has no tenant, assign the user to a tenant before performing file operations.
Example fix
// before
client.PostAsJsonAsync("/files/v1/request-upload-url", cmd); // token had no tenant claim
// after
request.Headers.Add("tenant", tenantId); // or use the tenant-routed host, with a freshly issued token Defensive patterns
Strategy: validation
Validate before calling
const tenantId = getTenantIdFromToken(token);
if (!tenantId) throw new Error("Token has no tenant claim — re-authenticate or send the tenant header"); Type guard
function hasTenant(claims) { return typeof claims.tenant === "string" && claims.tenant.length > 0; } Try / catch
try { await requestUploadUrl(cmd); } catch (e) { if (e.status === 401 && e.message === "invalid tenant") { await reauthenticateWithTenantContext(); } else throw e; } Prevention
- Always call the API through the tenant-routed host or include the tenant identifier header.
- Re-authenticate after any tenancy config change instead of reusing old tokens.
- Decode tokens in dev tooling to verify the tenant claim before debugging deeper.
When it happens
Trigger: Calling POST /files/v1/request-upload-url (RequestUploadUrlCommand) with an access token that lacks the tenant identifier claim — e.g. a token issued for a non-tenant context, a token whose 'tenant' claim was dropped, or an unauthenticated/misrouted request where the tenant could not be resolved from the header/host route.
Common situations: Tokens minted outside the normal login flow (service tokens, stale tokens from before tenancy was added); clients forgetting the tenant header/route the API host expects; hosting/mapping misconfiguration so Finbuckle cannot resolve the tenant from the request; testing with raw tokens copied from another environment.
Related errors
- invalid tenant
- Cross-tenant audit summary requires…
- Cross-tenant audit access requires…
- Tenant context is required.
- Only the root operator may generate invoices across tenants.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/3b6d5289267e8ca6.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Files/Modules.Files/Features/v1/RequestUploadUrl/RequestUploadUrlCommandHandler.cs:31
using Mediator;
using Microsoft.Extensions.Options;
namespace FSH.Modules.Files.Features.v1.RequestUploadUrl;
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}'.",View on GitHub (pinned to 3f2959e683)