fullstackhero/dotnet-starter-kit · critical · ForbiddenException

This tenant's subscription has expired. Please renew to…

Error message

This tenant's subscription has expired. Please renew to continue.

What it means

The multitenancy middleware throws ForbiddenException ("This tenant's subscription has expired. Please renew to continue.") when the current UTC time is past the tenant's ValidUpto plus the configured TenantBillingOptions.GracePeriodDays. Inside the grace period requests still pass, with a days-left warning header; after it, requests are blocked.

Solutions

  1. Renew the tenant subscription (extend ValidUpto via the billing/multitenancy admin APIs).
  2. Increase GracePeriodDays in TenantBillingOptions to allow a longer window.
  3. Verify server clock accuracy (NTP) if expiry seems premature.
  4. Correct an invalid ValidUpto value if it was set wrong during provisioning.

Example fix

// before
"TenantBilling": { "GracePeriodDays": 0 }
// after
"TenantBilling": { "GracePeriodDays": 14 }
Defensive patterns

Strategy: try-catch

Validate before calling

var daysLeft = (tenant.ValidUpto - DateTime.UtcNow).TotalDays;
if (daysLeft <= 3) triggerRenewalReminder();

Try / catch

try { await apiFetch(url); }
catch (ApiError e) when (e.Message.Contains("expired"))
{ showSubscriptionExpiredScreen(); }

Prevention

When it happens

Trigger: Any API request after ValidUpto + GracePeriodDays has elapsed for the tenant; clock skew between app servers making nowUtc appear later than it is; a subscription that lapsed without renewal.

Common situations: Credit card expired / payment failed and nobody renewed; grace period configured too short (GracePeriodDays=0); server clock drift triggering expiry early.

Related errors


AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15). Data as JSON: /api/errors/b4fab15533febec3. Report an issue: GitHub.

Appendix: source

Thrown at src/Modules/Multitenancy/Modules.Multitenancy/MultitenancyModule.cs:194

                }

                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);
                        var headerValue = daysLeft.ToString(System.Globalization.CultureInfo.InvariantCulture);
                        ctx.Response.OnStarting(static state =>
                        {
                            var (response, value) = ((HttpResponse, string))state;
                            response.Headers["X-Subscription-Grace"] = value;
                            return Task.CompletedTask;
                        }, (ctx.Response, headerValue));
                    }
                }
            }

View on GitHub (pinned to 3f2959e683)