Documentation

Detail Endpoints

← All docs

Detail Endpoints

Fetch and authorize a single entity in one call.

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

Usage

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

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:

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));