fullstackhero/dotnet-starter-kit · critical · ForbiddenException
This tenant has been deactivated. Contact your…
Error message
This tenant has been deactivated. Contact your administrator.
What it means
The multitenancy middleware in MultitenancyModule.ConfigureMiddleware throws ForbiddenException ("This tenant has been deactivated. Contact your administrator.") when the resolved tenant exists, is not the root tenant, and tenant.IsActive is false. Every request from a deactivated tenant is hard-blocked at the middleware level.
Solutions
- Reactivate the tenant (set IsActive=true via the multitenancy admin endpoint).
- Point the client to the correct active tenant id in its configuration.
- Renew/resolve the billing issue that led to deactivation, then reactivate.
- If this is the platform account, confirm you should be hitting the root tenant.
Example fix
// before (client config.json)
{ "tenantId": "acme-old" }
// after
{ "tenantId": "acme" } // tenant 'acme' is active Defensive patterns
Strategy: try-catch
Validate before calling
var tenantOk = await httpClient.GetFromJsonAsync<TenantStatus>($"/api/tenants/{tenantId}/status");
if (tenantOk is { IsActive: false }) redirectToRenewalPage(); Try / catch
try { await apiFetch(url); }
catch (ApiError e) when (e.Message.Contains("deactivated"))
{ showTenantDeactivatedScreen(); } Prevention
- Check tenant status at app bootstrap and show a maintenance/renewal screen.
- Monitor tenant IsActive flags in your ops dashboard.
- Automate renewal reminders before deactivation happens.
- Keep environment-specific tenant ids out of shared client config.
When it happens
Trigger: Any API request carrying a tenant id/header whose Tenant row has IsActive=false; requests to a tenant deactivated by an operator or by billing automation.
Common situations: Tenant deactivated for non-payment by an ops action; a test tenant turned off but clients still deployed against it; pointing a client at a staging/deactivated tenant id by config mistake.
Related errors
- This tenant's subscription has expired. Please renew to…
- Tenant context is required.
- Only the root operator may generate invoices across tenants.
- Tenant context is required.
- Invoice not found.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/7175e57aec293370.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Multitenancy/Modules.Multitenancy/MultitenancyModule.cs:183
if (!isOperator)
{
var accessor = ctx.RequestServices.GetRequiredService<IMultiTenantContextAccessor<AppTenantInfo>>();
var tenant = accessor.MultiTenantContext?.TenantInfo;
// Claim strategy no-ops pre-auth, so a JWT-only (no header) request may have no resolved
// tenant here — fall back to the caller's claim.
if (tenant is null && !string.IsNullOrEmpty(callerTenant))
{
var store = ctx.RequestServices.GetRequiredService<IMultiTenantStore<AppTenantInfo>>();
tenant = await store.GetAsync(callerTenant).ConfigureAwait(false);
}
if (tenant is not null &&
!string.Equals(tenant.Id, MultitenancyConstants.Root.Id, StringComparison.Ordinal))
{
if (!tenant.IsActive)
{
throw new ForbiddenException("This tenant has been deactivated. Contact your administrator.");
}
// Expiry is enforced on every request (not just at login) with a grace period:
// a tenant past ValidUpto still works until ValidUpto + grace, then is hard-blocked.
var graceDays = ctx.RequestServices
.GetRequiredService<IOptions<TenantBillingOptions>>().Value.GracePeriodDays;
var nowUtc = ctx.RequestServices.GetRequiredService<TimeProvider>().GetUtcNow().UtcDateTime;
var graceEndsUtc = tenant.ValidUpto.AddDays(graceDays);
if (nowUtc > graceEndsUtc)
{
throw new ForbiddenException("This tenant's subscription has expired. Please renew to continue.");
}
// Inside the grace period: surface days-left so clients can warn. Set via OnStarting so
// the header survives even when an exception handler rewrites the response.
if (nowUtc > tenant.ValidUpto)
{
var daysLeft = (int)Math.Ceiling((graceEndsUtc - nowUtc).TotalDays);View on GitHub (pinned to 3f2959e683)