fullstackhero/dotnet-starter-kit · error · ForbiddenException

Cross-tenant audit summary requires…

Error message

Cross-tenant audit summary requires Permissions.AuditTrails.ViewCrossTenant.

What it means

GetAuditSummaryQueryHandler.BuildBaseQueryAsync throws ForbiddenException when the caller lacks the AuditingPermissions.AuditTrails.ViewCrossTenant permission but requested a cross-tenant summary. The query needs IgnoreQueryFilters() to span tenants, which is only permitted for holders of that permission. Maps to HTTP 403.

Solutions

  1. Grant the AuditingPermissions.AuditTrails.ViewCrossTenant permission to the caller's role.
  2. If cross-tenant scope was not intended, request the summary scoped to the caller's own tenant.
  3. Re-run permission/claim seeding (DbMigrator --seed) so roles include the new permission.
  4. Use a root-operator account for platform-wide audit summaries.

Example fix

// before
role.Permissions = [Permissions.Users.View];

// after
role.Permissions = [Permissions.Users.View, AuditingPermissions.AuditTrails.ViewCrossTenant];
Defensive patterns

Strategy: validation

Validate before calling

bool canView = await permissions.HasPermissionAsync(userId, AuditingPermissions.AuditTrails.ViewCrossTenant, ct); if (!canView) scopeSummaryToOwnTenant();

Try / catch

try { return await api.GetAuditSummary(request); } catch (HttpRequestException e) when (e.StatusCode == HttpStatusCode.Forbidden) { return Results.Forbid(); }

Prevention

When it happens

Trigger: Calling the audit-summary endpoint with a cross-tenant request (tenant filter matching multiple tenants or 'all') while the authenticated user's permission set does not include ViewCrossTenant.

Common situations: Tenant admin attempting platform-wide reporting; role definitions missing the ViewCrossTenant claim after seeding; permission renamed/moved in a newer version so older roles lack it.

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


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

Appendix: source

Thrown at src/Modules/Auditing/Modules.Auditing/Features/v1/GetAuditSummary/GetAuditSummaryQueryHandler.cs:107

        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 summary requires Permissions.AuditTrails.ViewCrossTenant.");
        }

        return _dbContext.AuditRecords
            .AsNoTracking()
            .IgnoreQueryFilters()
            .Where(a => a.TenantId == requested);
    }

    private (DateTime FromUtc, DateTime ToUtc) ResolveWindow(DateTime? from, DateTime? to)
    {
        var now = _timeProvider.GetUtcNow().UtcDateTime;
        var resolvedTo = to ?? now;
        var resolvedFrom = from ?? resolvedTo - DefaultWindow;

        if (resolvedTo - resolvedFrom > MaxWindow)
        {
            resolvedFrom = resolvedTo - MaxWindow;
        }

View on GitHub (pinned to 3f2959e683)