fullstackhero/dotnet-starter-kit · error · UnauthorizedException
tenant is deactivated
Error message
tenant {tenant.Id} is deactivated What it means
ValidateTenantStatus throws UnauthorizedException with the tenant id in the message when the tenant record's IsActive flag is false. Root tenant is exempt. Called during credential login and refresh validation, a deactivated tenant blocks all of its users from obtaining or renewing tokens.
Solutions
- Re-enable the tenant: set IsActive=true on the tenant record (admin endpoint or tenants table).
- If deactivation is billing-related, resolve the subscription and re-activate; note the separate grace-period expiry throws 'validity has expired' instead.
- Verify the client is sending the intended tenant identifier — a typo may target a disabled tenant.
Example fix
// before UPDATE tenants SET is_active = false WHERE id = 'acme'; // after (after renewal confirmed) UPDATE tenants SET is_active = true WHERE id = 'acme';
Defensive patterns
Strategy: try-catch
Validate before calling
// when the API exposes tenant info, check before authenticating
const tenant = await api.get(`/api/tenants/${tenantId}`);
if (tenant && tenant.isActive === false) { showTenantDisabledScreen(); return; } Type guard
function isActiveTenant(t: { id: string; isActive: boolean } | null | undefined): t is { id: string; isActive: true } {
return !!t && t.isActive === true && t.id.length > 0;
} Try / catch
catch (ApiError e) when (e.StatusCode === 401 && /tenant .* is deactivated/.test(e.Message)) {
showMessage('Your workspace is deactivated. Contact your account manager to reactivate.');
haltAuthRetryLoop();
} Prevention
- Send the tenant identifier from validated configuration, not free-text input.
- Notify tenant admins before/when a tenant is deactivated so clients stop retrying.
- Track billing status and re-enable tenants promptly after renewal.
When it happens
Trigger: Any login/refresh request carrying a tenant header whose tenant row has IsActive=false — usually after billing-driven deactivation, an admin disabling the tenant, or upgrade/seed restoring IsActive incorrectly.
Common situations: Subscription lapsed and tenant was deactivated pending renewal; SaaS operator disabled a tenant for abuse/maintenance; client config pointing at the wrong tenant that happens to be disabled; restored database where IsActive flags were reset.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Unauthorized
- 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/bed90d44713a0b16.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Services/IdentityService.cs:286
throw new UnauthorizedException("user is deactivated");
}
if (!user.EmailConfirmed)
{
throw new UnauthorizedException("email not confirmed");
}
}
private void ValidateTenantStatus(AppTenantInfo tenant)
{
if (tenant.Id == MultitenancyConstants.Root.Id)
{
return;
}
if (!tenant.IsActive)
{
throw new UnauthorizedException($"tenant {tenant.Id} is deactivated");
}
// Honor the billing grace period: a lapsed tenant can still authenticate until
// ValidUpto + grace (matching the request-time guard in MultitenancyModule).
if (_timeProvider.GetUtcNow().UtcDateTime > tenant.ValidUpto.AddDays(_gracePeriodDays))
{
throw new UnauthorizedException($"tenant {tenant.Id} validity has expired");
}
}
private async Task<List<Claim>> BuildUserClaimsAsync(FshUser user, string tenantId, CancellationToken ct)
{
var claims = CreateBasicClaims(user, tenantId);
await AddRoleClaimsAsync(claims, user, ct);
return claims;
}
private static List<Claim> CreateBasicClaims(FshUser user, string tenantId)View on GitHub (pinned to 3f2959e683)