SqlOS

Fine-Grained Auth

Mutations

Authorize writes and create resources in the hierarchy.

3 sections

For create, update, and delete operations, check authorization first with CheckAccessAsync, then perform the mutation.

Create pattern#

  1. Check permission on the parent resource
  2. Create the FGA resource
  3. Create your domain entity with the resource ID
  4. Save in one transaction
CSHARP
app.MapPost("/api/chains/{chainId}/locations", async (
    string chainId,
    CreateLocationRequest request,
    ExampleAppDbContext context,
    ISqlOSFgaAuthService authService,
    HttpContext http) =>
{
    var subjectId = http.GetSubjectId();
    var chain = await context.Chains.FindAsync(chainId);
    if (chain == null) return Results.NotFound();
 
    var access = await authService.CheckAccessAsync(
        subjectId, "LOCATION_EDIT", chain.ResourceId);
    if (!access.Allowed)
        return Results.Json(new { error = "Permission denied" }, statusCode: 403);
 
    var resourceId = context.CreateResource(
        chain.ResourceId, request.Name, "location");
 
    var location = new Location
    {
        ResourceId = resourceId,
        ChainId = chainId,
        Name = request.Name,
        Address = request.Address
    };
    context.Locations.Add(location);
    await context.SaveChangesAsync();
 
    return Results.Created($"/api/locations/{location.Id}", location);
});

Update pattern#

CSHARP
app.MapPut("/api/chains/{id}", async (
    string id,
    UpdateChainRequest request,
    ExampleAppDbContext context,
    ISqlOSFgaAuthService authService,
    HttpContext http) =>
{
    var subjectId = http.GetSubjectId();
    var chain = await context.Chains.FindAsync(id);
    if (chain == null) return Results.NotFound();
 
    var access = await authService.CheckAccessAsync(
        subjectId, "CHAIN_EDIT", chain.ResourceId);
    if (!access.Allowed)
        return Results.Json(new { error = "Permission denied" }, statusCode: 403);
 
    chain.Name = request.Name;
    chain.Description = request.Description;
    await context.SaveChangesAsync();
 
    return Results.Ok(chain);
});

Delete pattern#

CSHARP
app.MapDelete("/api/chains/{id}", async (
    string id,
    ExampleAppDbContext context,
    ISqlOSFgaAuthService authService,
    HttpContext http) =>
{
    var subjectId = http.GetSubjectId();
    var chain = await context.Chains.FindAsync(id);
    if (chain == null) return Results.NotFound();
 
    var access = await authService.CheckAccessAsync(
        subjectId, "CHAIN_EDIT", chain.ResourceId);
    if (!access.Allowed)
        return Results.Json(new { error = "Permission denied" }, statusCode: 403);
 
    context.Chains.Remove(chain);
    await context.SaveChangesAsync();
 
    return Results.NoContent();
});