authz-core
A Zanzibar-inspired Fine-Grained Authorization engine for Rust, based on Google's globally consistent authorization system.
authz-core is the database- and transport-agnostic heart of an authorization system. It provides:
- A model DSL for declaring types, relations, and permissions
- A type system for validating tuples against the parsed model
- Datastore traits (
TupleReader,TupleWriter,PolicyReader,PolicyWriter) that you implement against any backend - A
CoreResolverthat walks the authorization graph, resolvingCheckrequests via depth-first or breadth-first traversal with configurable cycle detection - CEL condition evaluation for attribute-based access control (ABAC)
- A cache abstraction (
AuthzCache) for pluggable caching backends - A dispatcher abstraction for fan-out to multiple resolvers
No database or transport dependencies are included — those live in downstream crates (e.g. pgauthz).
Quick start
[]
= "0.1"
= { = "1", = ["rt-multi-thread", "macros"] }
1. Define your model
type user {}
type organization {
relations
define member: [user]
define admin: [user]
}
type document {
relations
define owner: [user]
define editor: [user | organization#member]
define viewer: [user | organization#member]
permissions
define can_view = viewer + editor + owner
define can_edit = editor + owner
}
2. Parse and build a TypeSystem
use parse_dsl;
use TypeSystem;
let model = parse_dsl.expect;
let type_system = new;
3. Wire up the resolver
use CoreResolver;
use StaticPolicyProvider;
use ;
let provider = new;
let resolver = new;
let req = new;
match resolver.resolve_check.await?
Model DSL
The DSL is a superset of the OpenFGA syntax.
Types and relations
type <name> {
relations
define <relation>: <expression>
permissions
define <permission> = <expression>
}
Relations are stored as tuples in the datastore. Permissions are derived — they cannot be the subject of a write tuple.
Relation expressions
| Syntax | Meaning |
|---|---|
[user] |
Direct assignment — only user subjects |
[user | group#member] |
Union of direct types and usersets |
[user:*] |
Public / wildcard — any user subject |
[user with ip_check] |
Conditional on a named CEL condition |
editor |
Computed userset — inherit from another relation |
parent->viewer |
Tuple-to-userset — traverse a relation, then check viewer on the target |
a + b |
Union |
a & b |
Intersection |
a - b |
Exclusion (a minus b) |
Operator precedence: + (union) binds tighter than &, which binds tighter than -.
Conditions (ABAC)
condition ip_check(allowed_cidrs: list<string>, request_ip: string) {
request_ip in allowed_cidrs
}
Supported parameter types: string, int, bool, list<string>, list<int>, list<bool>, map<string, string>.
The condition expression is a CEL expression evaluated at check time when the context map is populated in ResolveCheckRequest.
Module overview
| Module | Contents |
|---|---|
model_ast |
AST types: ModelFile, TypeDef, RelationDef, RelationExpr, ConditionDef |
model_parser |
parse_dsl(src) — parses the DSL into a ModelFile |
model_validator |
Post-parse semantic validation |
type_system |
TypeSystem — query types/relations, validate tuples |
traits |
Tuple, TupleFilter, TupleReader, TupleWriter, PolicyReader, PolicyWriter, RevisionReader |
resolver |
CheckResolver trait, ResolveCheckRequest, CheckResult, RecursionConfig |
core_resolver |
CoreResolver — the actual graph-walking engine |
policy_provider |
PolicyProvider trait + StaticPolicyProvider |
dispatcher |
Dispatcher trait + LocalDispatcher |
cache |
AuthzCache<V> trait + NoopCache + noop_cache() helper |
cel |
CEL expression compilation and evaluation |
tenant_schema |
ChangelogReader trait for the Watch API |
error |
AuthzError — all error variants |
Implementing the datastore traits
There are two categories of traits to implement:
- Tuple traits —
TupleReader/TupleWriter: read and write relationship tuples (e.g.document:42#viewer@user:alice). Used directly byCoreResolver. - Policy traits —
PolicyReader/PolicyWriter: read and write the authorization policy (the DSL source stored in the datastore). Used by your service layer to load and persist model definitions.
TupleReader
use async_trait;
use ;
use AuthzError;
PolicyReader
use async_trait;
use ;
use AuthzError;
Recursion and performance
ResolveCheckRequest accepts a RecursionConfig to tune resolution behaviour:
use ;
// Depth-first (default) — fast, memory-efficient
let cfg = depth_first.max_depth;
// Breadth-first — finds shortest path, higher memory
let cfg = breadth_first.cycle_detection;
ResolverMetadata (shared via Arc<AtomicU32>) tracks dispatch count, datastore queries, cache hits, and max depth reached across recursive calls — useful for observability.
Plugging in a cache
Implement AuthzCache<CheckResult> and pass it to CoreResolver:
use AuthzCache;
use CheckResult;
;
MSRV
Rust 1.85 or later (edition 2024).
License
Licensed under the Apache License, Version 2.0.