fullstackhero/dotnet-starter-kit · error · UnauthorizedException
Tenant context is required.
Error message
Tenant context is required.
What it means
GetMyTopupRequestsQueryHandler pins the query strictly to the caller's own tenant (TopupRequests is not tenant-filtered by the DbContext), so it needs tenantAccessor.MultiTenantContext?.TenantInfo?.Id. A null tenant id means 'my tenant' is undefined, and the handler throws UnauthorizedException('Tenant context is required.').
Solutions
- Add the tenant identifier to the request (__tenant__ header or the tenant's mapped hostname).
- Check the multitenancy strategy configuration matches how the client sends the tenant (header vs host vs path).
- Ensure Finbuckle middleware ordering is before UseEndpoints.
- In unit tests, provide a mock ITenantAccessor returning a TenantInfo with Id set.
Example fix
// before
var res = await client.GetAsync("/api/v1/wallets/my-topup-requests?pageNumber=1");
// after
client.DefaultRequestHeaders.Add("__tenant__", "acme");
var res = await client.GetAsync("/api/v1/wallets/my-topup-requests?pageNumber=1"); Defensive patterns
Strategy: try-catch
Validate before calling
if (tenantAccessor.MultiTenantContext?.TenantInfo?.Id is null)
throw new InvalidOperationException("GetMyTopupRequests needs a tenant context (__tenant__ header or tenant host)."); Type guard
bool HasTenant(ITenantAccessor a) => a.MultiTenantContext?.TenantInfo?.Id is not null;
Try / catch
try { var page = await api.GetMyTopupRequestsAsync(page); }
catch (UnauthorizedException ex) when (ex.Message == "Tenant context is required.")
{
// re-authenticate/resend with tenant identifier
} Prevention
- Keep tenant header injection in a shared API client wrapper.
- Verify host-based tenant resolution behind proxies with an e2e test.
- Seed tenant context in every integration test fixture.
- Fail fast in middleware when TenantInfo is null for tenant-scoped routes.
When it happens
Trigger: Calling the my-top-up-requests endpoint with no Finbuckle-resolvable tenant (missing __tenant__ header, unmapped host), or invoking the query from a non-HTTP context without tenant context.
Common situations: Omitting the tenant header in Swagger/curl after switching APIs; host-based tenant strategy broken behind a reverse proxy that rewrites Host; integration tests that forgot tenant seeding.
Related errors
- Tenant context is required.
- Tenant context is required.
- You can only approve top-up requests for your own tenant.
- Tenant context is required.
- Tenant context is required.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/6f8046165afad3e4.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Billing/Modules.Billing/Features/v1/Wallets/GetMyTopupRequests/GetMyTopupRequestsQueryHandler.cs:25
using FSH.Modules.Billing.Data;
using FSH.Modules.Billing.Mappings;
using Mediator;
using Microsoft.EntityFrameworkCore;
namespace FSH.Modules.Billing.Features.v1.Wallets.GetMyTopupRequests;
public sealed class GetMyTopupRequestsQueryHandler(
BillingDbContext dbContext,
IMultiTenantContextAccessor<AppTenantInfo> tenantAccessor)
: IQueryHandler<GetMyTopupRequestsQuery, PagedResponse<TopupRequestDto>>
{
public async ValueTask<PagedResponse<TopupRequestDto>> Handle(GetMyTopupRequestsQuery 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 q = dbContext.TopupRequests.AsNoTracking()
.Where(r => r.TenantId == tenantId);
if (query.Status is not null)
{
q = q.Where(r => r.Status == query.Status);
}
var total = await q.LongCountAsync(cancellationToken).ConfigureAwait(false);
var items = await q
.OrderByDescending(r => r.CreatedAtUtc)
.Skip((query.PageNumber - 1) * query.PageSize)
.Take(query.PageSize)
.ToListAsync(cancellationToken).ConfigureAwait(false);
return new PagedResponse<TopupRequestDto>
{View on GitHub (pinned to 3f2959e683)