Fine-Grained Authorization
Relationship-based access control — the WorkOS FGA / Google Zanzibar model. Permissions are relationship tuples ("warrants"), and a schema lets relations inherit, so you don't write a warrant for every permission. Self-hosted, no per-check pricing.
#Schema & inheritance
The schema declares each resource type's relations and how they derive:
Usersets like group#member expand recursively.
import { createFgaEngine, createNeonWarrantStore } from '@absolutejs/auth';
import type { FgaSchema } from '@absolutejs/auth';
// Relationship-based access control (the WorkOS FGA / Google Zanzibar model).
// Relations inherit: an editor is also a viewer; a doc inherits its folder's
// viewers (tupleToUserset); usersets (group#member) expand recursively.
const schema: FgaSchema = {
document: {
owner: { kind: 'self' },
editor: {
kind: 'union',
rules: [{ kind: 'self' }, { kind: 'computedUserset', relation: 'owner' }]
},
viewer: {
kind: 'union',
rules: [
{ kind: 'self' },
{ kind: 'computedUserset', relation: 'editor' },
{ kind: 'tupleToUserset', viaRelation: 'parent', relation: 'viewer' }
]
}
},
folder: { viewer: { kind: 'self' } }
};
export const fga = createFgaEngine({
schema,
warrantStore: createNeonWarrantStore(process.env.DATABASE_URL)
});#Warrants, check & query
Write warrants, then check (does X have a relation on Y?) or listSubjects (who has it?). The engine follows the schema's inheritance, with cycle protection and a depth limit.
// Write relationships (warrants), then check or query — the schema's inheritance
// does the rest, so you don't write a warrant per permission.
await fga.writeWarrant({
resourceType: 'document',
resourceId: 'doc1',
relation: 'owner',
subjectType: 'user',
subjectId: 'alice'
});
// doc1 lives in folder f1 (so it inherits f1's viewers):
await fga.writeWarrant({
resourceType: 'document',
resourceId: 'doc1',
relation: 'parent',
subjectType: 'folder',
subjectId: 'f1'
});
// Check API — does alice have <relation> on doc1?
await fga.check({
resourceType: 'document',
resourceId: 'doc1',
relation: 'viewer',
subjectType: 'user',
subjectId: 'alice'
}); // true: owner -> editor -> viewer
// Query API — who can view doc1? (expands every path, incl. group#member)
const viewers = await fga.listSubjects({
resourceType: 'document',
resourceId: 'doc1',
relation: 'viewer'
});#Reverse queries & DSL
listObjects answers the inverse of check — "which resources of this type can the subject access?" — by enumerating candidates and checking each (add a reverse index for very high object counts). parseSchema lets you author the schema in the OpenFGA-style DSL instead of the object form.
// Reverse query — which documents can alice view? (the inverse of check)
const docs = await fga.listObjects({
resourceType: 'document',
relation: 'viewer',
subjectType: 'user',
subjectId: 'alice'
}); // -> ['doc1', ...]
// Author the schema in the OpenFGA-style DSL and parse it, instead of the
// object form. Grammar (one statement per line):
// type document
// relations
// define owner: [user]
// define editor: [user] or owner
// define viewer: [user] or editor or viewer from parent
import { parseSchema } from '@absolutejs/auth';
const schema = parseSchema(dsl);#Caching
Pass a cache to memoize check (and the checks listObjects runs).
ttlMs — supply a shared FgaCache for a multi-instance deployment.import { createFgaEngine, createInMemoryCheckCache } from '@absolutejs/auth';
// Memoize check() with a TTL cache. Writes (writeWarrant/deleteWarrant) clear it
// on this instance; other instances see staleness up to ttlMs — supply a shared
// (e.g. Redis-backed) FgaCache for those.
const fga = createFgaEngine({
schema,
warrantStore,
cache: createInMemoryCheckCache({ ttlMs: 5000, maxEntries: 10000 })
});