Reference
FGA API Reference
Application-facing access checks, entity-backed resources, grants, and FGA query helpers.
This page documents the application-facing FGA surface. The recommended path is SqlOSDbContext<TContext> plus ISqlOSResourceEntity for protected domain rows, explicit subject provisioning, and explicit role grants. Public schema/bootstrap implementation classes are not intended as normal application extension points.
The core authorization service. Injected via DI as ISqlOSFgaAuthService.
Check if a subject has access to a specific resource with a given permission. Walks up the resource hierarchy from the target resource to the root, looking for grants whose role includes the required permission.
var access = await authService.CheckAccessAsync(subjectId, "CHAIN_VIEW", "chain-1");
if (!access.Allowed)
return Results.Json(new { error = "Permission denied" }, statusCode: 403);Parameters
| Name | Type | Description |
|---|---|---|
subjectId | string | The subject (user, agent, etc.) to check access for. |
permissionKey | string | The permission key to check (e.g., "CHAIN_VIEW"). |
resourceId | string | The resource ID to check access on. |
Returns
Task<SqlOSFgaAccessCheckResult>
| Field | Type | Description |
|---|---|---|
Allowed | bool | Whether the subject has the permission on the resource. |
Trace | List<SqlOSFgaAccessTrace>? | Step-by-step trace of how the decision was made. |
Error | string? | Error message if the check failed unexpectedly. |
Check whether a subject has a permission on the configured FGA root resource. Use it for an intentionally global capability granted at root; it does not scan for the permission on arbitrary descendants.
var canEdit = await authService.HasCapabilityAsync(subjectId, "CHAIN_EDIT");
if (!canEdit)
return Results.Json(new { error = "Permission denied" }, statusCode: 403);Parameters
| Name | Type | Description |
|---|---|---|
subjectId | string | The subject to check. |
permissionKey | string | The permission key to check. |
Returns
Task<bool> — the result of CheckAccessAsync(subjectId, permissionKey, RootResourceId).
Produce a LINQ expression that filters an IQueryable<T> to only entities the subject can access. The expression translates to a SQL Server table-valued function call at query time.
var filter = await authService
.GetAuthorizationFilterAsync<Chain>(subjectId, "CHAIN_VIEW");
var chains = await dbContext.Chains
.Where(filter)
.OrderBy(c => c.Name)
.ToListAsync();Parameters
| Name | Type | Description |
|---|---|---|
subjectId | string | The subject to filter for. |
permissionKey | string | The permission key that determines which resources are visible. |
Type constraint: T : IHasResourceId — the entity must expose a ResourceId property. Protected domain entities should usually implement ISqlOSResourceEntity, which extends IHasResourceId and lets SqlOSDbContext<TContext> sync backing resource rows during EF saves.
Returns
Task<Expression<Func<T, bool>>> — an EF Core-compatible expression you pass to .Where().
Point checks, traces, and EF authorization filters apply the same lifecycle rules automatically. Inactive users and groups, expired service accounts, inactive target resources, and resources below an inactive ancestor are denied even when a matching grant remains stored. Applications do not need to revoke every grant when deactivating one of these records, and should not add a separate display-only lifecycle filter around authorization queries.
Provision typed subjects with the SDK helpers rather than inserting only a bare SqlOSFgaSubject: authorization fails closed when a user, group, agent, or service_account subject is missing its corresponding typed lifecycle row.
Produce a detailed, structured trace of a resource access decision. Shows every step of the hierarchy walk and which grants matched or didn't. Useful for debugging authorization issues.
var trace = await authService.TraceResourceAccessAsync(
subjectId, "chain-1", "CHAIN_VIEW");
// trace contains the full decision pathParameters
| Name | Type | Description |
|---|---|---|
subjectId | string | The subject to trace access for. |
resourceId | string | The target resource. |
permissionKey | string | The permission to trace. |
Returns
Task<SqlOSFgaResourceAccessTrace> — structured trace of the full decision.
Canonical SDK helpers for resource lifecycle, subject provisioning, and role grants. Available via using SqlOS.Extensions.
For protected application rows, implement ISqlOSResourceEntity and let SqlOSDbContext<TContext> synchronize SqlOSFgaResource rows during SaveChanges / SaveChangesAsync.
public sealed class Workspace : ISqlOSResourceEntity
{
public Guid Id { get; set; }
public string ResourceId { get; set; } = "";
public string OrganizationId { get; set; } = "";
public string Name { get; set; } = "";
public string ResourceTypeId => "workspace";
public string ResourceName => Name;
public string ParentResourceId => $"org::{OrganizationId}";
public string? ResourceDescription => null;
public bool ResourceIsActive => true;
}Manual resource APIs remain available for resources that are not backed by domain entities:
| Method | Use |
|---|---|
CreateResourceAsync(...) | Strict create with a generated resource ID. |
CreateResourceWithIdAsync(...) | Strict create with an explicit stable resource ID. |
ProvisionResourceWithIdAsync(...) | Idempotent create/update for explicit non-entity resources. |
DeleteResourceAsync(...) | Delete one resource and its direct grants without cascading child resources. |
Subjects are provisioned explicitly. Grants never create subjects implicitly.
| Method | Use |
|---|---|
ProvisionUserSubjectAsync(...) | Idempotently provision a user FGA subject and typed user row. |
ProvisionAgentSubjectAsync(...) | Idempotently provision an agent FGA subject and typed agent row. |
ProvisionServiceAccountSubjectAsync(...) | Idempotently provision a service-account FGA subject and typed service-account row. |
GrantRoleAsync(...) | Idempotently grant a role by key or ID to an existing subject/resource. |
RevokeRoleAsync(...) | Remove a role grant by key or ID if it exists. |
Lower-level/read-side FGA convenience extensions. Available via using SqlOS.Fga.Extensions.
CreateResource(...) is a lower-level/manual lifecycle helper kept for manual FGA samples and existing code. Do not use it as the normal protected-entity creation path. For protected application rows, prefer ISqlOSResourceEntity plus SqlOSDbContext<TContext>.
// Manual lifecycle sample only.
var resourceId = context.CreateResource("retail_root", request.Name, "chain");Fetch a single entity by predicate, check authorization, and return the appropriate HTTP result in one call. Replaces the pattern of fetch → check → map → return.
return await authService.AuthorizedDetailAsync(
context.Chains.Include(c => c.Locations),
c => c.Id == id,
subjectId,
"CHAIN_VIEW",
chain => new ChainDetailDto
{
Id = chain.Id,
Name = chain.Name,
LocationCount = chain.Locations.Count
});Parameters
| Name | Type | Description |
|---|---|---|
authService | ISqlOSFgaAuthService | The auth service (called as extension method). |
query | IQueryable<TEntity> | The queryable to fetch from (can include .Include()). |
predicate | Expression<Func<TEntity, bool>> | Filter to find the single entity (e.g., c => c.Id == id). |
subjectId | string | The subject to authorize. |
permissionKey | string | The permission to check on the entity's resource. |
selector | Func<TEntity, TDto> | Maps the entity to a DTO for the response body. |
Type constraint: TEntity : class, IHasResourceId
Returns
Task<IResult> — one of:
| Condition | HTTP Result |
|---|---|
| Entity not found | 404 Not Found |
| Access denied | 403 { "error": "Permission denied" } |
| Access granted | 200 with the mapped DTO |
Fluent builder for creating cursor-paginated, sorted, searchable, authorization-filtered queries.
Start building a paged specification. The idSelector identifies the unique tiebreaker column for cursor pagination.
var spec = PagedSpec.For<Chain>(c => c.Id)
.RequirePermission("CHAIN_VIEW")
.SortByString("name", c => c.Name, isDefault: true)
.SortByString("description", c => c.Description ?? "")
.Search(search, c => c.Name, c => c.Description)
.Configure(q => q.Include(c => c.Locations))
.Build(pageSize, cursor, sortBy, sortDir);Builder Methods
| Method | Parameters | Description |
|---|---|---|
For<T>(idSelector) | Expression<Func<T, string>> | Start builder with the ID/tiebreaker column. |
.RequirePermission(key) | string | Set the FGA permission key for authorization filtering. |
.SortByString(name, selector, isDefault?) | string, Expression<Func<T, string>>, bool | Register a named string sort column. |
.Search(search, ...fields) | string?, params Expression<Func<T, string?>>[] | Add text search across the given fields. |
.Where(predicate) | Expression<Func<T, bool>> | Add a static filter. |
.Configure(configurator) | Func<IQueryable<T>, IQueryable<T>> | Return a configured query (for example query => query.Include(...)). |
.Build(pageSize, cursor, sortBy, sortDir) | int, string?, string?, string? | Build the final PagedSpecification<T>. |
The exact query-configuration signature is:
public PagedSpecificationBuilder<T> Configure(
Func<IQueryable<T>, IQueryable<T>> configurator)The function must return the query it configures. An Action<IQueryable<T>> is not accepted.
Executes a PagedSpecification<T> against a queryable, combining user filters with FGA authorization and cursor pagination. Injected via DI.
Execute a paged specification and return a paginated result.
var result = await executor.ExecuteAsync(
context.Chains, spec, subjectId,
c => new ChainDto
{
Id = c.Id,
Name = c.Name,
LocationCount = c.Locations.Count
});
// result.Data — the page of DTOs
// result.PageSize — the clamped page size used
// result.NextCursor — pass to the next request
// result.HasNextPage — whether NextCursor is non-nullParameters
| Name | Type | Description |
|---|---|---|
dbSet / query | DbSet<TEntity> or IQueryable<TEntity> | The base query. |
specification | PagedSpecification<TEntity> | The spec built by PagedSpec.For<T>().Build(). |
subjectId | string | The subject for FGA authorization filtering. |
selector | Func<TEntity, TDto> | Maps entities to DTOs. |
cancellationToken | CancellationToken | Optional cancellation token. |
Type constraint: TEntity : class, IHasResourceId
Returns
Task<PaginatedResult<TDto>>
| Field | Type | Description |
|---|---|---|
Data | List<TDto> | The current page of results. |
PageSize | int | The page size used by the result. |
NextCursor | string? | Opaque cursor for the next page. null if no more pages. |
HasNextPage | bool | true when NextCursor is not null. |
Count total matching entities (with authorization filtering) without fetching data.
var total = await executor.CountAsync(context.Chains, spec, subjectId);Parameters
| Name | Type | Description |
|---|---|---|
dbSet | DbSet<TEntity> | The base query. |
specification | PagedSpecification<TEntity> | The spec. |
subjectId | string | The subject for authorization. |
Returns
Task<long> — total count of accessible, matching entities.
Recommended resource lifecycle contract for protected application rows. It extends IHasResourceId and gives SqlOSDbContext<TContext> enough metadata to create, update, and delete backing FGA resources during EF saves.
public interface ISqlOSResourceEntity : IHasResourceId
{
string ResourceTypeId { get; }
string ResourceName { get; }
string? ParentResourceId { get; }
string? ResourceDescription { get; }
bool ResourceIsActive { get; }
}Lower-level filter contract that exposes an entity's FGA resource ID. Required for GetAuthorizationFilterAsync, AuthorizedDetailAsync, and ISpecificationExecutor. ISqlOSResourceEntity extends this interface and is the recommended contract for normal protected domain rows.
public interface IHasResourceId
{
string ResourceId { get; }
}Lower-level implementation
public class Chain : IHasResourceId
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string ResourceId { get; set; } = "";
public string Name { get; set; } = "";
}public class SqlOSFgaAccessCheckResult
{
public bool Allowed { get; set; }
public List<SqlOSFgaAccessTrace>? Trace { get; set; }
public string? Error { get; set; }
}public class SqlOSFgaAccessTrace
{
public string Step { get; set; }
public string Detail { get; set; }
public string? ResourceId { get; set; }
public string? ResourceName { get; set; }
public string? GrantId { get; set; }
public string? RoleName { get; set; }
public string? SubjectName { get; set; }
}public class SqlOSFgaResource
{
public string Id { get; set; }
public string ParentId { get; set; }
public string Name { get; set; }
public string ResourceTypeId { get; set; }
public bool IsActive { get; set; }
}public class SqlOSFgaGrant
{
public string Id { get; set; }
public string SubjectId { get; set; }
public string ResourceId { get; set; }
public string RoleId { get; set; }
public string? Description { get; set; }
}public class SqlOSFgaSubject
{
public string Id { get; set; }
public string SubjectTypeId { get; set; }
public string DisplayName { get; set; }
public string? OrganizationId { get; set; }
public string? ExternalRef { get; set; }
}