Interview question
Authorize a loaded resource by ownership
Loads a resource once, evaluates owner-or-capability authorization, and avoids trusting route ownership claims.
TL;DR
Loads a resource once, evaluates owner-or-capability authorization, and avoids trusting route ownership claims.
Resource authorization, ownership, capability overrides, enumeration resistance, and handler boundaries.
Practice the problem like a real interview: restate, reason, implement, and test.
A user may read their own invoice. Support staff with invoices.read.any may read any invoice. Everyone else receives the endpoint's deliberate not-found response.
Changing the route id to another customer's invoice never bypasses the resource authorization handler.
I load the invoice using only its id, then pass the actual entity to IAuthorizationService. The handler compares trusted identity data to the entity and supports one explicit override capability.
public sealed record ReadInvoiceRequirement : IAuthorizationRequirement;
public sealed class ReadInvoiceHandler
: AuthorizationHandler<ReadInvoiceRequirement, Invoice>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
ReadInvoiceRequirement requirement, Invoice invoice)
{
var userId = context.User.TryGetUserId();
var canReadAny = context.User.HasClaim("capability", "invoices.read.any");
if (userId == invoice.OwnerId || canReadAny)
context.Succeed(requirement);
return Task.CompletedTask;
}
}
app.MapGet("/invoices/{id:guid}", async Task<IResult> (Guid id,
AppDbContext db, IAuthorizationService authorization,
ClaimsPrincipal user, CancellationToken ct) =>
{
var invoice = await db.Invoices.AsNoTracking().SingleOrDefaultAsync(x => x.Id == id, ct);
if (invoice is null) return Results.NotFound();
var allowed = await authorization.AuthorizeAsync(user, invoice,
new ReadInvoiceRequirement());
return allowed.Succeeded
? Results.Ok(InvoiceResponse.From(invoice))
: Results.NotFound();
}).RequireAuthorization();
Returning 404 for both absent and unauthorized ids reduces resource enumeration in this contract. Other products may choose 403 for transparency, but the choice should be consistent and logged safely.
Next in API Implementation Labs: Secure Upload