fullstackhero/dotnet-starter-kit · error · UnauthorizedException
Unauthorized
Error message
Unauthorized
What it means
GetValidatedTenant throws UnauthorizedException when Finbuckle's MultiTenantContext has no TenantInfo, meaning the request reached the Identity service without a resolved tenant. The multitenancy middleware could not derive a tenant identifier (header, route, or host strategy) from the incoming request, so authentication is refused with a generic 401.
Solutions
- Add the tenant identifier to the request per the configured Finbuckle strategy — typically the 'tenant' HTTP header for header strategy.
- Verify the hostname used matches a tenant mapping when using the host strategy; check appsettings Multitenancy section.
- Confirm Finbuckle multitenancy middleware is registered before the endpoints in Program.cs and the strategy configured (WithHeaderStrategy/WithHostStrategy).
Example fix
// before
curl -X POST https://localhost:7030/api/tokens -H 'Content-Type: application/json' -d '{...}'
// after
curl -X POST https://localhost:7030/api/tokens -H 'tenant: root' -H 'Content-Type: application/json' -d '{...}' Defensive patterns
Strategy: validation
Validate before calling
const tenant = config.tenantId ?? localStorage.getItem('tenant');
if (!tenant) throw new Error('tenant identifier must be sent with every request');
headers['tenant'] = tenant; Try / catch
catch (ApiError e) when (e.StatusCode == 401) { redirectToTenantSetup(); } Prevention
- Centralize tenant header injection in the HTTP client interceptor so it can never be forgotten.
- Document the required tenant header in the API client README/onboarding.
- Add a smoke test that asserts login fails fast with a clear message when the tenant header is absent.
When it happens
Trigger: Calling any authentication endpoint (login, refresh, GetProfile) without a tenant identifier header (e.g. missing 'tenant' header in a header-strategy setup), or calling the API on a hostname that Finbuckle's host strategy does not map to a tenant. The call 'tenant' path invokes GetValidatedTenant which finds MultiTenantContext.TenantInfo null.
Common situations: New API clients forget the tenant header; integration scripts that worked against a single-tenant deployment; reverse-proxy stripping host headers; Finbuckle strategy misconfiguration (wrong header name or case); testing tools (curl/Postman) missing the header.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- tenant is deactivated
- missing tenant context
- missing tenant context
- refresh token is invalid or expired
- user is deactivated
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/cae5c6f40607b31a.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Services/IdentityService.cs:175
if (userRoleIds.Count > 0)
{
var roleNames = await _dbContext.Roles
.IgnoreQueryFilters()
.Where(r => userRoleIds.Contains(r.Id) && EF.Property<string>(r, "TenantId") == tenantId)
.Select(r => r.Name!)
.ToListAsync(ct);
claims.AddRange(roleNames.Select(r => new Claim(ClaimTypes.Role, r)));
}
return (user.Id, claims);
}
private AppTenantInfo GetValidatedTenant()
{
var tenant = _multiTenantContextAccessor!.MultiTenantContext.TenantInfo
?? throw new UnauthorizedException();
if (string.IsNullOrWhiteSpace(tenant.Id))
{
throw new UnauthorizedException();
}
return tenant;
}
private async Task<FshUser> FindAndValidateUserByCredentialsAsync(string email, string password)
{
var user = await _userManager.FindByEmailAsync(email.Trim().Normalize());
if (user is null)
{
// Generic 401 — never confirm or deny account existence from this path.
throw new UnauthorizedException();
}
View on GitHub (pinned to 3f2959e683)