fullstackhero/dotnet-starter-kit · error · UnauthorizedException
Tenant context is required.
Error message
Tenant context is required.
What it means
CreateTopupRequestCommandHandler resolves the caller's own tenant id because BillingDbContext is not tenant-filtered and the TopupRequest must be created with an explicit TenantId. When tenantAccessor.MultiTenantContext?.TenantInfo?.Id is null (no tenant resolved by Finbuckle), it throws UnauthorizedException('Tenant context is required.') instead of writing an orphan (tenant-less) request.
Solutions
- Send the request with the tenant identifier: header __tenant__: <tenant-id-or-slug> or the mapped tenant host.
- Verify the tenant exists in the tenant store/connection-string mappings used by Finbuckle's strategy.
- Ensure multitenancy middleware is registered before endpoints in the host.
- In tests, stub ITenantAccessor to return TenantInfo with a non-null Id.
Example fix
// before
await client.PostAsJsonAsync("/api/v1/wallets/topup-requests", new { amount = 100 });
// after
client.DefaultRequestHeaders.Add("__tenant__", "acme");
await client.PostAsJsonAsync("/api/v1/wallets/topup-requests", new { amount = 100 }); Defensive patterns
Strategy: validation
Validate before calling
var tenant = request.Headers.TryGetValues("__tenant__", out var v) ? v.FirstOrDefault() : tenantFromHost;
if (string.IsNullOrWhiteSpace(tenant)) return Results.Unauthorized(); Type guard
bool HasTenant(ITenantAccessor a) => a.MultiTenantContext?.TenantInfo?.Id is not null;
Try / catch
try { await api.CreateTopupRequestAsync(amount, note); }
catch (UnauthorizedException ex) when (ex.Message == "Tenant context is required.")
{
// prompt user/tenant selection and retry with __tenant__ header
} Prevention
- Ensure the tenant is registered in the tenant store before its users call wallet endpoints.
- Inject __tenant__ in one place (client interceptor) rather than per-call.
- Add integration tests that create top-up requests per tenant.
- Check middleware order after upgrading Finbuckle or the host pipeline.
When it happens
Trigger: Posting a create-top-up-request command without a resolvable tenant identifier (no __tenant__ header, unmapped hostname), or invoking the handler from a console/job context with no tenant context configured.
Common situations: curl/Postman tests missing the tenant header; a new tenant not yet registered in the tenant store so Finbuckle can't resolve it; CI integration tests omitting tenant setup; proxies stripping the host header used for tenant resolution.
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/e4df12a983f25e8d.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Billing/Modules.Billing/Features/v1/Wallets/CreateTopupRequest/CreateTopupRequestCommandHandler.cs:24
using FSH.Modules.Billing.Data;
using FSH.Modules.Billing.Domain;
using Mediator;
namespace FSH.Modules.Billing.Features.v1.Wallets.CreateTopupRequest;
public sealed class CreateTopupRequestCommandHandler(
BillingDbContext db,
IMultiTenantContextAccessor<AppTenantInfo> tenantAccessor,
ICurrentUser currentUser)
: ICommandHandler<CreateTopupRequestCommand, Guid>
{
public async ValueTask<Guid> Handle(CreateTopupRequestCommand command, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
// 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 requestedBy = currentUser.IsAuthenticated() ? currentUser.GetUserId().ToString() : null;
var request = TopupRequest.Create(tenantId, command.Amount, "USD", command.Note, requestedBy);
db.TopupRequests.Add(request);
await db.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
return request.Id;
}
}
View on GitHub (pinned to 3f2959e683)