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

  1. Ensure the request carries the tenant identifier expected by the Finbuckle resolver (header/route/host/claim)
  2. Check middleware order — UseMultiTenancy must run before endpoint mapping and before the service executes
  3. For background jobs, set the tenant context explicitly before invoking the service
  4. 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

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


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)