{"record":{"id":"7175e57aec293370","repo":"fullstackhero/dotnet-starter-kit","slug":"this-tenant-has-been-deactivated-contact-your-administrator","errorCode":null,"errorMessage":"This tenant has been deactivated. Contact your administrator.","messagePattern":"This tenant has been deactivated\\. Contact your administrator\\.","errorType":"exception","errorClass":"ForbiddenException","httpStatus":403,"severity":"critical","filePath":"src/Modules/Multitenancy/Modules.Multitenancy/MultitenancyModule.cs","lineNumber":183,"sourceCode":"            if (!isOperator)\n            {\n                var accessor = ctx.RequestServices.GetRequiredService<IMultiTenantContextAccessor<AppTenantInfo>>();\n                var tenant = accessor.MultiTenantContext?.TenantInfo;\n\n                // Claim strategy no-ops pre-auth, so a JWT-only (no header) request may have no resolved\n                // tenant here — fall back to the caller's claim.\n                if (tenant is null && !string.IsNullOrEmpty(callerTenant))\n                {\n                    var store = ctx.RequestServices.GetRequiredService<IMultiTenantStore<AppTenantInfo>>();\n                    tenant = await store.GetAsync(callerTenant).ConfigureAwait(false);\n                }\n\n                if (tenant is not null &&\n                    !string.Equals(tenant.Id, MultitenancyConstants.Root.Id, StringComparison.Ordinal))\n                {\n                    if (!tenant.IsActive)\n                    {\n                        throw new ForbiddenException(\"This tenant has been deactivated. Contact your administrator.\");\n                    }\n\n                    // Expiry is enforced on every request (not just at login) with a grace period:\n                    // a tenant past ValidUpto still works until ValidUpto + grace, then is hard-blocked.\n                    var graceDays = ctx.RequestServices\n                        .GetRequiredService<IOptions<TenantBillingOptions>>().Value.GracePeriodDays;\n                    var nowUtc = ctx.RequestServices.GetRequiredService<TimeProvider>().GetUtcNow().UtcDateTime;\n                    var graceEndsUtc = tenant.ValidUpto.AddDays(graceDays);\n                    if (nowUtc > graceEndsUtc)\n                    {\n                        throw new ForbiddenException(\"This tenant's subscription has expired. Please renew to continue.\");\n                    }\n\n                    // Inside the grace period: surface days-left so clients can warn. Set via OnStarting so\n                    // the header survives even when an exception handler rewrites the response.\n                    if (nowUtc > tenant.ValidUpto)\n                    {\n                        var daysLeft = (int)Math.Ceiling((graceEndsUtc - nowUtc).TotalDays);","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Multitenancy/Modules.Multitenancy/MultitenancyModule.cs#L165-L201","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (client config.json)\n{ \"tenantId\": \"acme-old\" }\n// after\n{ \"tenantId\": \"acme\" } // tenant 'acme' is active","handlingStrategy":"try-catch","validationCode":"var tenantOk = await httpClient.GetFromJsonAsync<TenantStatus>($\"/api/tenants/{tenantId}/status\");\nif (tenantOk is { IsActive: false }) redirectToRenewalPage();","typeGuard":null,"tryCatchPattern":"try { await apiFetch(url); }\ncatch (ApiError e) when (e.Message.Contains(\"deactivated\"))\n{ showTenantDeactivatedScreen(); }","preventionTips":["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."],"tags":["multitenancy","billing","forbidden","middleware"],"backgroundTag":"tenant-deactivated","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}