Round #240
07 May 2026, 20:35· csharp· meta/llama-3.3-70b-instruct· r13-clean-hints
- Complexity
- band 1
- ZP / UO / OBS
- 5 / 5 / 1
- Journey weight
- 825
- Lightshift ΔL
- +0.40
Prompt
Implement tenant-scoped authorization that prevents cross-tenant access
Zero-point index (what the engine surfaced)
ZPI-ZPI-0008 | 2026-05-07T20:35:26Z
Implement tenant-scoped authorization that prevents cross-tenant access
ZERO POINT — 5 entries (structural invariants)
● hsl(178.91, 100%, 21%) IAuthorizationHandler [contract]
● hsl(14.32, 100%, 21%) IAuthorizationRequirement [contract]
● hsl(349.17, 100%, 21%) AuthorizationHandler<TRequirement> [contract]
● hsl(83.28, 100%, 15%) ITenantInfo [contract]
● hsl(281.88, 100%, 15%) IMultiTenantStore<TTenantInfo> [contract]
UNOBSERVED — 5 entries (implied truths)
○ hsl(27.69, 100%, 50%) async [feature]
○ hsl(49.23, 100%, 30%) try [feature]
○ hsl(112.62, 100%, 16%) Token validation parameters [function]
○ hsl(31.38, 100%, 40%) Scoped registration [function]
○ hsl(44.31, 100%, 25%) IOptions of T binding [function]
OBSERVED — 1 entries (explicit in request)
◉ hsl(102.56, 100%, 21%) scoped [feature]
COMPLEXITY │ 11 entries │ spread: 85° │ band: MEDIUM
Baseline response (no ZPI)
Implementing Tenant-Scoped Authorization
=====================================
To prevent cross-tenant access, we can implement a tenant-scoped authorization system. This system will ensure that users can only access data and resources within their own tenant.
### Requirements
* .NET 6 or later
* ASP.NET Core 6 or later
* Entity Framework Core 6 or later
### Database Schema
We will use a simple database schema with two tables: `Tenants` and `Users`. Each tenant will have a unique identifier, and each user will be associated with a tenant.
```sql
CREATE TABLE Tenants (
Id INT PRIMARY KEY,
Name NVARCHAR(255) NOT NULL
);
CREATE TABLE Users (
Id INT PRIMARY KEY,
TenantId INT NOT NULL,
Username NVARCHAR(255) NOT NULL,
PasswordHash NVARCHAR(255) NOT NULL,
FOREIGN KEY (TenantId) REFERENCES Tenants(Id)
);
```
### Tenant-Scoped Authorization Implementation
We will create a custom authorization attribute to check if the user is accessing data within their own tenant.
```csharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Threading.Tasks;
public class TenantScopedAuthorizationAttribute : AuthorizeAttribute, IAuthorizationRequirement
{
public string TenantIdClaim { get; set; } = "tenantId";
public TenantScopedAuthorizationAttribute()
{
}
}
public class TenantScopedAuthorizationHandler : AuthorizationHandler<TenantScopedAuthorizationAttribute>
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantScopedAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, TenantScopedAuthorizationAttribute requirement)
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
return;
}
var tenantIdClaim = httpContext.User.FindFirstValue(requirement.TenantIdClaim);
if (string.IsNullOrEmpty(tenantIdClaim))
{
return;
}
var currentTenantId = httpContext.Request.Headers["TenantId"].FirstOrDefault();
if (currentTenantId != tenantIdClaim)
{
return;
}
context.Succeed(requirement);
}
}
```
### Registering the Authorization Handler
We need to register the authorization handler in the `Startup.cs` file.
```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("TenantScoped", policy => policy.Requirements.Add(new TenantScopedAuthorizationAttribute()));
});
services.AddSingleton<IAuthorizationHandler, TenantScopedAuthorizationHandler>();
}
```
### Using the Tenant-Scoped Authorization Attribute
We can now use the `TenantScopedAuthorizationAttribute` to decorate our controllers and actions.
```csharp
[Authorize(Policy = "TenantScoped")]
[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
// Only accessible within the user's own tenant
return Ok();
}
}
```
### Setting the Tenant ID Claim
We need to set the tenant ID claim when the user logs in.
```csharp
[ApiController]
[Route("api/[controller]")]
public class LoginController : ControllerBase
{
[HttpPost]
public IActionResult Login(LoginModel model)
{
// Authenticate the user
var user = _userRepository.GetUser(model.Username, model.Password);
if (user == null)
{
return Unauthorized();
}
// Set the tenant ID claim
var claims = new[]
{
new Claim("tenantId", user.TenantId.ToString())
};
var identity = new ClaimsIdentity(claims, "login");
var principal = new ClaimsPrincipal(identity);
// Sign in the user
HttpContext.SignInAsync(principal);
return Ok();
}
}
```
### Setting the Current Tenant ID
We need to set the current tenant ID in the HTTP request headers.
```csharp
[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
// Set the current tenant ID
HttpContext.Request.Headers["TenantId"] = User.FindFirstValue("tenantId");
// Only accessible within the user's own tenant
return Ok();
}
}
```
Example Use Case
---------------
Let's say we have two tenants: `Tenant A` and `Tenant B`. Each tenant has its own set of users.
* `Tenant A` has users `User 1` and `User 2`.
* `Tenant B` has users `User 3` and `User 4`.
When `User 1` logs in, the tenant ID claim is set to `Tenant A`. When `User 1` tries to access a resource, the current tenant ID is set to `Tenant A`. The `TenantScopedAuthorizationAttribute`
Enhanced response (with ZPI)
```csharp
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading.Tasks;
// Define a class to hold tenant information
public class TenantInfo
{
public Guid Id { get; set; }
public string Name { get; set; }
}
// Define a class to hold token validation parameters
public class TokenValidationParameters
{
public string TenantIdClaim { get; set; }
}
// Define a class to implement ITenantInfo
public class TenantInfoImpl : ITenantInfo
{
public Guid Id { get; set; }
public string Name { get; set; }
}
// Define a class to implement IMultiTenantStore<TTenantInfo>
public class MultiTenantStore : IMultiTenantStore<TenantInfoImpl>
{
private readonly Dictionary<Guid, TenantInfoImpl> _tenants = new Dictionary<Guid, TenantInfoImpl>();
public Task AddAsync(TenantInfoImpl tenant)
{
_tenants[tenant.Id] = tenant;
return Task.CompletedTask;
}
public Task<TenantInfoImpl> GetAsync(Guid id)
{
if (_tenants.TryGetValue(id, out var tenant))
{
return Task.FromResult(tenant);
}
return Task.FromResult<TenantInfoImpl>(null);
}
}
// Define a class to implement IAuthorizationRequirement
public class TenantAuthorizationRequirement : IAuthorizationRequirement
{
public Guid TenantId { get; set; }
}
// Define a class to implement IAuthorizationHandler
public class TenantAuthorizationHandler : AuthorizationHandler<TenantAuthorizationRequirement>
{
private readonly IMultiTenantStore<TenantInfoImpl> _multiTenantStore;
private readonly IOptions<TokenValidationParameters> _tokenValidationParameters;
public TenantAuthorizationHandler(IMultiTenantStore<TenantInfoImpl> multiTenantStore, IOptions<TokenValidationParameters> tokenValidationParameters)
{
_multiTenantStore = multiTenantStore;
_tokenValidationParameters = tokenValidationParameters;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, TenantAuthorizationRequirement requirement)
{
var tenantIdClaim = context.User.FindFirstValue(_tokenValidationParameters.Value.TenantIdClaim);
if (string.IsNullOrEmpty(tenantIdClaim))
{
context.Fail();
return;
}
var tenantId = Guid.Parse(tenantIdClaim);
if (tenantId != requirement.TenantId)
{
context.Fail();
return;
}
var tenant = await _multiTenantStore.GetAsync(tenantId);
if (tenant == null)
{
context.Fail();
return;
}
context.Succeed(requirement);
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("TenantPolicy", policy => policy.Requirements.Add(new TenantAuthorizationRequirement()));
});
services.AddSingleton<IMultiTenantStore<TenantInfoImpl>, MultiTenantStore>();
services.AddSingleton<IAuthorizationHandler, TenantAuthorizationHandler>();
services.Configure<TokenValidationParameters>(options =>
{
options.TenantIdClaim = "tenant_id";
});
}
}
```
This implementation provides tenant-scoped authorization by using a custom `TenantAuthorizationRequirement` and `TenantAuthorizationHandler`. The `TenantAuthorizationHandler` checks if the tenant ID in the token matches the required tenant ID, and if the tenant exists in the multi-tenant store. If both conditions are met, the authorization succeeds.
The `IMultiTenantStore<TTenantInfo>` interface is used to store and retrieve tenant information, and the `IOptions<T>` interface is used to bind the token validation parameters.
In the `Startup` class, the `AddAuthorization` method is used to add the custom policy, and the `AddSingleton` method is used to register the `IMultiTenantStore<TTenantInfo>` and `IAuthorizationHandler` instances. The `Configure` method is used to bind the token validation parameters.
To use this implementation, you can decorate your controllers or actions with the `[Authorize(Policy = "TenantPolicy")]` attribute, and pass the required tenant ID as a parameter to the `TenantAuthorizationRequirement` constructor. For example:
```csharp
[Authorize(Policy = "TenantPolicy")]
public class MyController : Controller
{
public IActionResult MyAction([FromServices] IAuthorizationService authorizationService)
{
var requirement = new TenantAuthorizationRequirement { TenantId = Guid.Parse("tenant_id") };
if (authorizationService.AuthorizeAsync(User, requirement).Result.Succeeded)
{
// Authorization succeeded
}
else
{
// Authorization failed
}
return View();
}
}
```
Grader chain
Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.
V1claude-haiku-4-5-20251001
BetterV4qwen2.5-coder-7b-instruct
BetterComments
Our grader said what it said. What do you say? Comment as a guest below.
No comments yet. Be the first to say what you make of this round.