SqlOS

Fine-Grained Auth

Detail Endpoints

Fetch and authorize a single entity in one call.

4 sections

AuthorizedDetailAsync fetches a single entity, checks authorization, and returns the appropriate HTTP result. It handles 404, 403, and 200 in one call.

Usage#

CSHARP
app.MapGet("/api/chains/{id}", async (
    string id,
    ExampleAppDbContext context,
    ISqlOSFgaAuthService authService,
    HttpContext http) =>
{
    var subjectId = http.GetSubjectId();
 
    return await authService.AuthorizedDetailAsync(
        context.Chains.Include(c => c.Locations),
        c => c.Id == id,
        subjectId, "CHAIN_VIEW",
        chain => new ChainDetailDto
        {
            Id = chain.Id,
            ResourceId = chain.ResourceId,
            Name = chain.Name,
            Description = chain.Description,
            LocationCount = chain.Locations.Count,
            CreatedAt = chain.CreatedAt
        });
});

What it returns#

ConditionHTTP result
Entity not found404 Not Found
Subject lacks permission403 { error: "Permission denied" }
Access granted200 with mapped DTO

Method signature#

CSHARP
Task<IResult> AuthorizedDetailAsync<TEntity, TDto>(
    IQueryable<TEntity> query,
    Expression<Func<TEntity, bool>> predicate,
    string subjectId,
    string permissionKey,
    Func<TEntity, TDto> selector) where TEntity : class, IHasResourceId;

When to use#

Use AuthorizedDetailAsync for any GET /resource/{id} endpoint. It replaces the common pattern of:

CSHARP
var entity = await query.FirstOrDefaultAsync(e => e.Id == id);
if (entity == null) return Results.NotFound();
 
var access = await authService.CheckAccessAsync(subjectId, "CHAIN_VIEW", entity.ResourceId);
if (!access.Allowed) return Results.Json(new { error = "Permission denied" }, statusCode: 403);
 
return Results.Ok(MapToDto(entity));