fullstackhero/dotnet-starter-kit · error · UnauthorizedException
invalid tenant
Error message
invalid tenant
What it means
EnsureValidTenant throws UnauthorizedException('invalid tenant') when the Finbuckle MultiTenantContext has no TenantInfo.Id for the current context. It guards tenant-scoped existence checks and image URL updates so they never execute outside a tenant scope, protecting tenant isolation.
Solutions
- Pass tenant resolution data on every request (tenant header, __tenant__ parameter, or mapped host).
- In background/non-HTTP contexts, set the multitenant context explicitly before calling the service.
- Verify Finbuckle middleware registration/order and the tenant store contents.
- Configure a default tenant if the deployment is effectively single-tenant.
Example fix
// before (job code, no tenant context)
await userProfileService.ExistsWithEmailAsync(email);
// after
using (multiTenantContextAccessor.MultiTenantContext.SetTenantInfo(
new TenantInfo("tenant-1", "tenant-1", null), replace: false))
{
await userProfileService.ExistsWithEmailAsync(email);
} Defensive patterns
Strategy: validation
Validate before calling
const tenantId = headers['X-Tenant-Id'] ?? new URLSearchParams(location.search).get('__tenant__');
if (!tenantId) throw new Error('No tenant resolved; call would throw "invalid tenant"'); Try / catch
try { await existsService.ExistsWithEmailAsync(email); }
catch (e) { if (e.status === 401 && e.message.includes('invalid tenant')) { resolveTenantThenRetry(); } else { throw e; } } Prevention
- In Hangfire jobs/background code, set the multitenant context explicitly before calling tenant-scoped services.
- Never call tenant-scoped services during app startup/migration.
- Send tenant identifiers on every API request from custom clients.
- Add a smoke test asserting tenant resolution before these endpoints.
When it happens
Trigger: Calling SetImageUrlAsync, ExistsWithEmailAsync, ExistsWithNameAsync, or ExistsWithPhoneNumberAsync from a context with no resolved tenant (background job, migration host, or request lacking tenant identifiers).
Common situations: Calling these services from Hangfire jobs or startup code where the tenant resolver never ran; scripts hitting endpoints without tenant headers; Finbuckle store missing the host mapping; middleware ordering issues.
Related errors
- Unauthorized
- 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/89651d0ca01a4fc5.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Services/UserProfileService.cs:160
}
public async Task<bool> ExistsWithNameAsync(string name, CancellationToken cancellationToken = default)
{
EnsureValidTenant();
return await userManager.FindByNameAsync(name) is not null;
}
public async Task<bool> ExistsWithPhoneNumberAsync(string phoneNumber, string? exceptId = null, CancellationToken cancellationToken = default)
{
EnsureValidTenant();
return await userManager.Users.FirstOrDefaultAsync(x => x.PhoneNumber == phoneNumber, cancellationToken) is FshUser user && user.Id != exceptId;
}
private void EnsureValidTenant()
{
if (string.IsNullOrWhiteSpace(multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id))
{
throw new UnauthorizedException("invalid tenant");
}
}
private string? ResolveImageUrl(Uri? imageUrl)
{
if (imageUrl is null)
{
return null;
}
// Absolute URLs (e.g., S3) pass through unchanged.
if (imageUrl.IsAbsoluteUri)
{
return imageUrl.ToString();
}
// For relative paths from local storage, prefix with the API origin and wwwroot.
if (_originUrl is null)View on GitHub (pinned to 3f2959e683)