fullstackhero/dotnet-starter-kit · error · UnauthorizedException
Tenant context is required.
Error message
Tenant context is required.
What it means
GetMyWalletQueryHandler resolves the caller's tenant id to get-or-create the wallet scoped to that tenant, because the billing store is not tenant-filtered and wallets are keyed by TenantId. With tenantAccessor.MultiTenantContext?.TenantInfo?.Id null, the handler cannot determine whose wallet to return and throws UnauthorizedException('Tenant context is required.').
Solutions
- Send the tenant identifier with the request (__tenant__ header or mapped tenant host).
- Register/verify the tenant in Finbuckle's tenant store so resolution succeeds.
- Ensure multitenancy middleware runs before the endpoint pipeline.
- In tests, fake ITenantAccessor to return TenantInfo with a fixed Id.
Example fix
// before
var res = await client.GetAsync("/api/v1/wallets/my");
// after
client.DefaultRequestHeaders.Add("__tenant__", "acme");
var res = await client.GetAsync("/api/v1/wallets/my"); Defensive patterns
Strategy: try-catch
Validate before calling
if (tenantAccessor.MultiTenantContext?.TenantInfo?.Id is null)
throw new InvalidOperationException("Cannot resolve wallet owner: no tenant context."); Type guard
bool HasTenant(ITenantAccessor a) => a.MultiTenantContext?.TenantInfo?.Id is not null;
Try / catch
try { var wallet = await api.GetMyWalletAsync(); }
catch (UnauthorizedException ex) when (ex.Message == "Tenant context is required.")
{
// set __tenant__ header / enter tenant scope, then retry once
} Prevention
- Provision tenants in the tenant store as part of onboarding before wallet calls.
- Include tenant header in service-to-service calls (propagate from incoming request).
- Test get-or-create wallet flows per tenant in CI.
- Keep multitenancy middleware first in the pipeline.
When it happens
Trigger: Requesting my-wallet without a resolvable tenant (no __tenant__ header, unmapped hostname), or calling the handler from a background job/CLI without a tenant context.
Common situations: New tenant not present in the tenant store so Finbuckle returns null TenantInfo; dashboard client deployed without runtime config supplying the tenant; service-to-service calls that dropped the tenant header.
Related errors
- Tenant context is required.
- Tenant context is required.
- Tenant context is required.
- Only the root operator may generate invoices across tenants.
- Tenant context is required.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/2bbdd0434af30993.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Billing/Modules.Billing/Features/v1/Wallets/GetMyWallet/GetMyWalletQueryHandler.cs:23
using FSH.Modules.Billing.Contracts.v1.Wallets;
using FSH.Modules.Billing.Mappings;
using FSH.Modules.Billing.Services;
using Mediator;
namespace FSH.Modules.Billing.Features.v1.Wallets.GetMyWallet;
public sealed class GetMyWalletQueryHandler(
IBillingService billingService,
IMultiTenantContextAccessor<AppTenantInfo> tenantAccessor)
: IQueryHandler<GetMyWalletQuery, WalletDto>
{
public async ValueTask<WalletDto> Handle(GetMyWalletQuery query, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(query);
// BillingDbContext is not tenant-filtered; resolve caller's own tenant and scope strictly to it.
var tenantId = tenantAccessor.MultiTenantContext?.TenantInfo?.Id
?? throw new UnauthorizedException("Tenant context is required.");
var wallet = await billingService.GetOrCreateWalletAsync(tenantId, "USD", cancellationToken).ConfigureAwait(false);
return wallet.ToDto();
}
}
View on GitHub (pinned to 3f2959e683)