Getting started
Getting started
Install
npm install agentauthz # or: bun add / pnpm add / yarn add
npm install agentauthz kysely # kysely only if you use the Postgres backendCompiled ESM with type declarations — plain Node ≥ 20 consumes it, no bundler required.
Declare a vocabulary
The engine accepts only actions you declare. There is no action wildcard.
import { actionRegistryFromCatalogue } from "agentauthz/ports";
const actions = actionRegistryFromCatalogue([
{ action: "docs.read", title: "Read documents" },
{ action: "docs.list", derivesFrom: "docs.read" },
{ action: "docs.write", derivesFrom: "docs.read", title: "Write documents" },
]);A statement naming docs.read covers docs.list and docs.write too — allow and deny alike.
Derivation is registry data: a policy author cannot invent it.
Wire the engine
import { PgAuthzStore } from "agentauthz/backends/pg";
import { makeAuthz } from "agentauthz/core";
const store = new PgAuthzStore(db, {
scopeKinds: ["root", "project"],
rootScope: { kind: "root", id: "*" },
});
const authz = makeAuthz({
grantStore: store,
auditSink: store,
actionRegistry: actions,
scopeKinds: ["root", "project"],
defaultScopeChain: [{ kind: "root", id: "*" }],
conditionKeys: {
"app:Region": { type: "string" },
},
});db is your own Kysely instance — the authz_* tables live in your database. See
Storage & conformance for the migrations.
Create a policy and grant it
const policy = await store.createPolicy({
name: "doc-reader",
description: "Read access to all documents",
statements: [
{ effect: "allow", actions: ["docs.read"], resources: ["doc:*"] },
],
createdBy: null,
});
await store.createGrant({
policyId: policy.id,
subject: { kind: "user", id: "alice" },
scope: { kind: "root", id: "*" },
createdBy: null,
});Check
await authz.check(
[{ kind: "user", id: "alice" }], // the caller's resolved subjects
"docs.list", // covered via derivation from docs.read
"doc:42",
);
// => true — and an audit row was written
Every check is audited from inside the seam. Nothing granted ⇒ false; a deny anywhere in the scope
chain beats an allow from anywhere else.
Identity is your job: resolve the caller into explicit
{kind, id}
subjects before they reach the engine — including any normalization such as lowercasing emails. The
library stores no principals; a dangling reference simply never matches.