Fine-Grained Auth
Syncing Auth to FGA
Sync AuthServer users and memberships into FGA subjects and grants.
When using both AuthServer and FGA, you need to sync authenticated users into the FGA model so they can be authorized. The typical active-session pattern is to sync on login.
The short example below reconciles an active membership and removes the opposite mapped role. Production role changes and offboarding must also reconcile when the membership, user, or organization changes; otherwise an old FGA grant can survive. Follow Turn AuthServer memberships into FGA access for the complete lifecycle, tenant predicates, and the current FGA IsActive caveat.
This page maps an AuthServer membership to a direct FGA grant. If the application's source of truth uses teams, follow Authorize teams with FGA groups for group membership, inherited grants, and removal semantics.
After login, provision the authenticated user as an FGA subject, provision any non-entity organization resource, and map the auth membership to an explicit FGA grant:
public async Task EnsureUserAccessAsync(
string subjectId, string organizationId, CancellationToken ct)
{
var user = await _context.Set<SqlOSUser>()
.FirstAsync(x => x.Id == subjectId && x.IsActive, ct);
var organization = await _context.Set<SqlOSOrganization>()
.FirstAsync(x => x.Id == organizationId && x.IsActive, ct);
var membership = await _context.Set<SqlOSMembership>()
.FirstAsync(x => x.UserId == subjectId
&& x.OrganizationId == organizationId
&& x.IsActive, ct);
await _context.ProvisionUserSubjectAsync(
subjectId,
displayName: user.DisplayName,
email: user.DefaultEmail,
organizationId: organizationId,
externalRef: subjectId,
isActive: user.IsActive,
cancellationToken: ct);
var organizationResourceId = $"org::{organizationId}";
await _context.ProvisionResourceWithIdAsync(
organizationResourceId,
resourceTypeId: "organization",
name: organization.Name,
parentResourceId: "root",
isActive: organization.IsActive,
cancellationToken: ct);
var roleKey = membership.Role is "admin" or "owner"
? "org_admin"
: "org_member";
await _context.RevokeRoleAsync(
subjectId,
organizationResourceId,
roleKey == "org_admin" ? "org_member" : "org_admin",
ct);
await _context.GrantRoleAsync(subjectId, organizationResourceId, roleKey, ct);
await _context.SaveChangesAsync(ct);
}ProvisionUserSubjectAsync -- creates or updates the FGA subject and typed FGA user row for the authenticated user.
ProvisionResourceWithIdAsync -- creates or updates the organization resource under root. This is a manual resource because the organization is not represented by an ISqlOSResourceEntity in this example.
RevokeRoleAsync / GrantRoleAsync -- remove the stale mapped role and create an idempotent grant linking the subject to the current role on the organization resource:
| Auth role | FGA role |
|---|---|
admin or owner | org_admin (full access) |
| everything else | org_member (read access) |
Call EnsureUserAccessAsync after successful login, during token creation:
if (!string.IsNullOrWhiteSpace(tokens.OrganizationId))
{
await fgaService.EnsureUserAccessAsync(
user.Id, tokens.OrganizationId, ct);
}The example app calls this in the token response helper, so every login automatically ensures the user has the right FGA grants based on their auth membership.
Also reconcile from membership role changes, membership removal/deactivation, and user or organization deactivation. Revoke only the explicit role keys owned by this mapping so unrelated manual grants remain intact. The complete guide includes an offboarding-safe implementation.
FGA subjects and grants are stored in tables optimized for the authorization query (tree walk + table-valued function). By syncing auth data into FGA at login time, authorization checks run entirely in SQL without joining against auth tables at query time.