fullstackhero/dotnet-starter-kit · error · ForbiddenException
Cross-tenant audit access requires…
Error message
Cross-tenant audit access requires Permissions.AuditTrails.ViewCrossTenant.
What it means
GetAuditsQueryHandler.BuildBaseQueryAsync throws ForbiddenException when a cross-tenant audit listing is requested without the AuditingPermissions.AuditTrails.ViewCrossTenant permission. Cross-tenant listing uses IgnoreQueryFilters(), gated behind this explicit permission check. Maps to HTTP 403.
Solutions
- Assign AuditingPermissions.AuditTrails.ViewCrossTenant to the required role.
- Restrict the UI tenant filter to the caller's tenant when the permission is absent.
- Re-run seeding to refresh role permissions.
- Log in as a root operator for cross-tenant audit views.
Defensive patterns
Strategy: validation
Validate before calling
if (requestedTenant != currentTenant && !user.HasPermission(AuditingPermissions.AuditTrails.ViewCrossTenant)) return Forbid();
Try / catch
try { return await api.GetAudits(filter); } catch (ForbiddenAccessException) { // render 'insufficient permissions' state } Prevention
- Only show the all-tenants filter to users with ViewCrossTenant
- Keep role permission catalogs in sync with server permission constants
- Audit role assignments periodically for least privilege
When it happens
Trigger: Calling the audit list endpoint with a tenant filter selecting a tenant other than the caller's (or 'all') while lacking ViewCrossTenant.
Common situations: Tenant admin filtering the audit grid by another tenant; UI exposing a tenant dropdown the user is not entitled to use; roles not re-seeded after the permission was introduced.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- Cross-tenant audit summary requires…
- Tenant context is required.
- Only the root operator may generate invoices across tenants.
- 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/24a95cb890f6c5e1.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Auditing/Modules.Auditing/Features/v1/GetAudits/GetAuditsQueryHandler.cs:160
var currentTenant = _currentUser.GetTenant();
var requested = string.IsNullOrWhiteSpace(query.TenantId) ? null : query.TenantId;
bool wantsCrossTenant =
requested is not null
&& !string.Equals(requested, currentTenant, StringComparison.OrdinalIgnoreCase);
if (!wantsCrossTenant)
{
return _dbContext.AuditRecords.AsNoTracking();
}
var userId = _currentUser.GetUserId().ToString();
var allowed = await _permissions
.HasPermissionAsync(userId, AuditingPermissions.AuditTrails.ViewCrossTenant, ct)
.ConfigureAwait(false);
if (!allowed)
{
throw new ForbiddenException("Cross-tenant audit access requires Permissions.AuditTrails.ViewCrossTenant.");
}
return _dbContext.AuditRecords
.AsNoTracking()
.IgnoreQueryFilters()
.Where(a => a.TenantId == requested);
}
/// <summary>
/// Clamps the supplied window to <see cref="MaxWindow"/> and supplies a
/// <see cref="DefaultWindow"/> when both endpoints are missing. The
/// validator catches obvious misuse (from > to); this method handles
/// the open-ended "no range" case so the SQL is always bounded.
/// </summary>
private (DateTime FromUtc, DateTime ToUtc) ResolveWindow(DateTime? from, DateTime? to)
{
var now = _timeProvider.GetUtcNow().UtcDateTime;
var resolvedTo = to ?? now;View on GitHub (pinned to 3f2959e683)