fullstackhero/dotnet-starter-kit · error · UnauthorizedException
invalid tenant
Error message
invalid tenant
What it means
Thrown as an UnauthorizedException by the private EnsureValidTenant guard when the current Finbuckle MultiTenantContext has no tenant ID. Every major operation in UserRegistrationService calls this guard first, so any identity operation without a resolved tenant is rejected as unauthorized (401-style). It protects tenant isolation: user data must never be touched without an active tenant.
Solutions
- Ensure the request carries the tenant identifier expected by the Finbuckle resolver (header/route/host/claim)
- Check middleware order — UseMultiTenancy must run before endpoint mapping and before the service executes
- For background jobs, set the tenant context explicitly before invoking the service
- Verify tenant resolver configuration in the API host (strategy, supported tenants)
Example fix
// before
client.DefaultRequestHeaders.Add("Accept", "application/json");
// after
client.DefaultRequestHeaders.Add("__tenant__", "tenant-abc");
client.DefaultRequestHeaders.Add("Accept", "application/json"); Defensive patterns
Strategy: validation
Validate before calling
var tenantId = httpContextAccessor.HttpContext?.GetMultiTenantContext()?.TenantInfo?.Id; if (string.IsNullOrWhiteSpace(tenantId)) throw new UnauthorizedAccessException("Request has no tenant context."); Type guard
bool HasTenant(FshUser? user) => user is not null && !string.IsNullOrWhiteSpace(user.TenantId);
Try / catch
catch (UnauthorizedException ex) when (ex.Message == "invalid tenant") { logger.LogError("Missing tenant context for request {Path}", path); return Results.Unauthorized(); } Prevention
- Send the tenant identifier header on every client request
- Keep UseMultiTenancy early in the middleware pipeline
- Set an explicit tenant scope in Hangfire jobs and other non-HTTP entry points
- Add a smoke test asserting identity endpoints reject tenant-less requests
When it happens
Trigger: Request missing the tenant identifier (e.g. __tenant__ header/route/claim resolver found nothing); middleware order issue so multitenancy didn't run; background job or CLI path constructing the service without a tenant context.
Common situations: Client forgot the tenant header after a base-URL change; new endpoint registered before the multitenancy middleware; Hangfire job or startup code calling registration services outside an HTTP tenant scope; misconfigured tenant resolver strategy.
Related errors
- ConnectionString can't be null.
- Tenant context is required.
- Tenant context is required.
- missing tenant context
- invalid tenant
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/5ef7a14ec9cb8847.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Services/UserRegistrationService.cs:161
var user = await userManager.Users
.Where(u => u.Id == userId && !u.PhoneNumberConfirmed)
.FirstOrDefaultAsync(cancellationToken);
_ = user ?? throw new CustomException("An error occurred while confirming phone number.");
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await userManager.ChangePhoneNumberAsync(user, user.PhoneNumber!, code);
return result.Succeeded
? string.Format(CultureInfo.InvariantCulture, "Phone number {0} confirmed successfully.", user.PhoneNumber)
: throw new CustomException(string.Format(CultureInfo.InvariantCulture, "An error occurred while confirming phone number {0}", user.PhoneNumber));
}
private void EnsureValidTenant()
{
if (string.IsNullOrWhiteSpace(multiTenantContextAccessor?.MultiTenantContext?.TenantInfo?.Id))
{
throw new UnauthorizedException("invalid tenant");
}
}
private static string ExtractEmailFromPrincipal(ClaimsPrincipal principal)
{
return principal.FindFirstValue(ClaimTypes.Email)
?? principal.FindFirstValue("email")
?? throw new CustomException("Email claim is required for external authentication.");
}
private async Task<FshUser> CreateUserFromPrincipalAsync(ClaimsPrincipal principal, string email)
{
var (firstName, lastName, userName) = ExtractUserInfoFromPrincipal(principal, email);
userName = await EnsureUniqueUserNameAsync(userName);
var user = new FshUser
{View on GitHub (pinned to 3f2959e683)