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

  1. Add the tenant identifier to the request per the configured Finbuckle strategy — typically the 'tenant' HTTP header for header strategy.
  2. Verify the hostname used matches a tenant mapping when using the host strategy; check appsettings Multitenancy section.
  3. 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

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

Related errors


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)