https://github.com/xraph/warden
Warden is a composable authorization engine supporting RBAC, ABAC, and ReBAC in a single unified API. It answers "are you allowed to do this?" and integrates natively with the Forge ecosystem.
https://github.com/xraph/warden
authz permissions
Last synced: 3 months ago
JSON representation
Warden is a composable authorization engine supporting RBAC, ABAC, and ReBAC in a single unified API. It answers "are you allowed to do this?" and integrates natively with the Forge ecosystem.
- Host: GitHub
- URL: https://github.com/xraph/warden
- Owner: xraph
- Created: 2026-02-20T14:48:20.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-03-28T20:54:20.000Z (3 months ago)
- Last Synced: 2026-03-28T22:25:53.490Z (3 months ago)
- Topics: authz, permissions
- Language: Go
- Homepage: https://warden.xraph.com
- Size: 401 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Warden — Composable permissions & authorization engine
[](https://pkg.go.dev/github.com/xraph/warden)
[](https://go.dev)
[](https://github.com/xraph/warden/actions/workflows/ci.yml)
Warden is a composable authorization engine supporting **RBAC**, **ABAC**, and **ReBAC** in a single unified API. It answers "are you allowed to do this?" and integrates natively with the Forge ecosystem.
## Features
- **RBAC** — Roles, permissions, role inheritance, resource-scoped assignments
- **ABAC** — Attribute-based policies with conditions (IP ranges, time windows, regex, etc.)
- **ReBAC** — Zanzibar-style relation tuples with BFS graph walking
- **Multi-model** — Use RBAC, ABAC, and ReBAC together; explicit deny > allow > default deny
- **Multi-tenant** — All data is tenant-scoped via `forge.Scope` or standalone context helpers
- **Extensible** — Plugin hooks for audit logging, metrics, and custom lifecycle events
- **Caching** — Built-in in-memory LRU cache with TTL and per-tenant/subject invalidation
- **Forge native** — Drop-in Forge extension with DI, API routes, and middleware
## Quick Start
```go
package main
import (
"context"
"fmt"
"log"
"github.com/xraph/warden"
"github.com/xraph/warden/assignment"
"github.com/xraph/warden/id"
"github.com/xraph/warden/permission"
"github.com/xraph/warden/role"
"github.com/xraph/warden/store/memory"
)
func main() {
ctx := warden.WithTenant(context.Background(), "myapp", "tenant-1")
s := memory.New()
eng, err := warden.NewEngine(warden.WithStore(s))
if err != nil {
log.Fatal(err)
}
// Create role + permission.
roleID := id.NewRoleID()
permID := id.NewPermissionID()
_ = s.CreateRole(ctx, &role.Role{ID: roleID, TenantID: "tenant-1", Name: "editor", Slug: "editor"})
_ = s.CreatePermission(ctx, &permission.Permission{ID: permID, TenantID: "tenant-1", Name: "doc:read", Resource: "doc", Action: "read"})
_ = s.AttachPermission(ctx, roleID, permID)
// Assign role to user.
_ = s.CreateAssignment(ctx, &assignment.Assignment{
ID: id.NewAssignmentID(), TenantID: "tenant-1",
RoleID: roleID, SubjectKind: "user", SubjectID: "alice",
})
// Check authorization.
result, err := eng.Check(ctx, &warden.CheckRequest{
Subject: warden.Subject{Kind: warden.SubjectUser, ID: "alice"},
Action: warden.Action{Name: "read"},
Resource: warden.Resource{Type: "doc", ID: "d1"},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Allowed: %v\n", result.Allowed) // true
}
```
## Installation
```bash
go get github.com/xraph/warden
```
## Architecture
```
Check Request
|
v
+----------+ +---------+ +---------+
| RBAC | --> | ReBAC | --> | ABAC |
| roles & | | relation| | policy |
| perms | | tuples | | engine |
+----------+ +---------+ +---------+
| | |
+-------+--------+-------+------+
| |
mergeDecisions explicit deny
| > allow
v > default deny
CheckResult
```
The engine evaluates all three models and merges results:
1. **Explicit deny** (from ABAC) always wins
2. **Any allow** from any model grants access
3. **Default deny** if no rules match
## Authorization Models
### RBAC (Role-Based Access Control)
Roles contain permissions. Users are assigned roles (globally or scoped to specific resources). Roles support inheritance via parent chains.
### ReBAC (Relationship-Based Access Control)
Zanzibar-style relation tuples (`object#relation@subject`) with BFS graph walking for transitive permissions. Subject sets enable hierarchical access (e.g., folder membership granting document access).
### ABAC (Attribute-Based Access Control)
Policies with conditions evaluate against subject attributes, resource attributes, and request context. Supported operators: `eq`, `neq`, `in`, `not_in`, `contains`, `starts_with`, `gt`, `lt`, `ip_in_cidr`, `time_after`, `time_before`, `regex`, and more.
## Store Backends
Warden ships with four pluggable store backends. All implement the composite `Store` interface from `store/store.go`.
| Backend | Package | Use case |
| --- | --- | --- |
| Memory | `store/memory` | Testing, development |
| PostgreSQL | `store/postgres` | Production (Grove ORM, migrations, transactions) |
| SQLite | `store/sqlite` | Embedded / edge deployments (Grove ORM) |
| MongoDB | `store/mongo` | NoSQL / document-oriented workloads (BSON, compound indexes) |
```go
import "github.com/xraph/warden/store/memory"
import "github.com/xraph/warden/store/postgres"
import "github.com/xraph/warden/store/sqlite"
import "github.com/xraph/warden/store/mongo"
// Memory (no config needed)
s := memory.New()
// PostgreSQL / SQLite — pass a Grove database instance
s := postgres.New(groveDB)
// MongoDB — pass a Grove MongoDB instance
s := mongo.New(mongoDB)
// All stores support Migrate() for schema setup
_ = s.Migrate(ctx)
```
## Plugin System
Warden provides a granular plugin system with **18 lifecycle hooks**. Plugins implement the base `plugin.Plugin` interface (just `Name() string`) and opt in to specific hooks by implementing additional interfaces.
```go
import "github.com/xraph/warden/plugin"
type AuditPlugin struct{}
func (p *AuditPlugin) Name() string { return "audit" }
// Opt in to the hooks you care about:
func (p *AuditPlugin) RoleCreated(ctx context.Context, r *role.Role) error {
log.Printf("role created: %s", r.Name)
return nil
}
func (p *AuditPlugin) AfterCheck(ctx context.Context, req, result any) error {
log.Printf("check completed")
return nil
}
```
### Available Hooks
| Category | Hook | Trigger |
| --- | --- | --- |
| Check | `BeforeCheck(ctx, req)` | Before authorization evaluation |
| Check | `AfterCheck(ctx, req, result)` | After authorization evaluation |
| Roles | `RoleCreated(ctx, role)` | Role created |
| Roles | `RoleUpdated(ctx, role)` | Role modified |
| Roles | `RoleDeleted(ctx, roleID)` | Role removed |
| Permissions | `PermissionCreated(ctx, perm)` | Permission created |
| Permissions | `PermissionDeleted(ctx, permID)` | Permission removed |
| Permissions | `PermissionAttached(ctx, roleID, permID)` | Permission attached to role |
| Permissions | `PermissionDetached(ctx, roleID, permID)` | Permission detached from role |
| Assignments | `RoleAssigned(ctx, assignment)` | Role assigned to subject |
| Assignments | `RoleUnassigned(ctx, assignment)` | Role unassigned from subject |
| Relations | `RelationWritten(ctx, tuple)` | Relation tuple created |
| Relations | `RelationDeleted(ctx, relID)` | Relation tuple removed |
| Policies | `PolicyCreated(ctx, policy)` | ABAC policy created |
| Policies | `PolicyUpdated(ctx, policy)` | ABAC policy modified |
| Policies | `PolicyDeleted(ctx, polID)` | ABAC policy removed |
| Lifecycle | `Shutdown(ctx)` | Engine shutting down |
Hook errors are logged as warnings but never block the caller.
## Caching
Warden includes a built-in in-memory LRU cache for `Check()` results with TTL-based expiration and scoped invalidation.
```go
import "github.com/xraph/warden/cache"
c := cache.NewMemory(
cache.WithTTL(5 * time.Minute), // Default: 5m
cache.WithMaxSize(10000), // Default: 10,000 entries
)
eng, _ := warden.NewEngine(
warden.WithStore(s),
warden.WithCache(c),
)
```
Cache keys are scoped by `tenantID:subjectKind:subjectID:action:resourceType:resourceID`.
### Invalidation
```go
// Invalidate all cached results for a tenant.
c.InvalidateTenant(ctx, "tenant-1")
// Invalidate all cached results for a specific subject.
c.InvalidateSubject(ctx, "tenant-1", warden.SubjectUser, "alice")
```
The cache automatically invalidates when roles, permissions, or assignments are modified through the engine.
## Middleware
Warden provides HTTP middleware for Forge route protection.
```go
import wardenmw "github.com/xraph/warden/middleware"
// Require a single permission — returns 403 if denied.
router.GET("/documents/:id", handler,
forge.WithMiddleware(wardenmw.Require(eng, "read", "document")),
)
// RequireAny — allows if ANY check passes (OR logic).
router.POST("/admin/action", handler,
forge.WithMiddleware(wardenmw.RequireAny(eng,
warden.CheckRequest{Action: warden.Action{Name: "admin"}, Resource: warden.Resource{Type: "system"}},
warden.CheckRequest{Action: warden.Action{Name: "write"}, Resource: warden.Resource{Type: "config"}},
)),
)
// RequireAll — allows only if ALL checks pass (AND logic).
router.DELETE("/documents/:id", handler,
forge.WithMiddleware(wardenmw.RequireAll(eng,
warden.CheckRequest{Action: warden.Action{Name: "delete"}, Resource: warden.Resource{Type: "document"}},
warden.CheckRequest{Action: warden.Action{Name: "admin"}, Resource: warden.Resource{Type: "document"}},
)),
)
```
Subject resolution priority:
1. Authenticated user ID from `forge.UserIDFromContext()`
2. Falls back to `unknown:anonymous`
## Configuration
```go
eng, err := warden.NewEngine(
warden.WithStore(store), // Required: store backend
warden.WithCache(cache), // Optional: check result cache
warden.WithConfig(warden.Config{
MaxGraphDepth: 10, // ReBAC graph traversal depth (default: 10)
CacheTTL: 5 * time.Minute, // Check result cache TTL (0 = disabled)
EnableRBAC: ptrBool(true), // Enable RBAC evaluation (default: true)
EnableABAC: ptrBool(true), // Enable ABAC evaluation (default: true)
EnableReBAC: ptrBool(true), // Enable ReBAC evaluation (default: true)
}),
warden.WithPlugin(auditPlugin), // Optional: lifecycle plugins
warden.WithEvaluator(customEvaluator), // Optional: custom ABAC evaluator
warden.WithGraphWalker(customWalker), // Optional: custom ReBAC graph walker
warden.WithLogger(logger), // Optional: structured logger
)
```
## ID System
Warden uses **TypeID** — UUIDv7-based, K-sortable, URL-safe identifiers with type prefixes for all entities.
```text
role_01h2xcejqtf2nbrexx3vqjhp41
perm_01h2xcejqtf2nbrexx3vqjhp41
asgn_01h2xcejqtf2nbrexx3vqjhp41
```
### Prefixes
| Prefix | Entity | Constructor |
| --- | --- | --- |
| `role` | Role | `id.NewRoleID()` |
| `perm` | Permission | `id.NewPermissionID()` |
| `asgn` | Assignment | `id.NewAssignmentID()` |
| `wpol` | Policy | `id.NewPolicyID()` |
| `rel` | Relation | `id.NewRelationID()` |
| `chklog` | Check log | `id.NewCheckLogID()` |
| `rtype` | Resource type | `id.NewResourceTypeID()` |
| `cond` | Condition | `id.NewConditionID()` |
```go
import "github.com/xraph/warden/id"
// Create
roleID := id.NewRoleID() // role_01h2xce...
permID := id.NewPermissionID() // perm_01h2xce...
// Parse
parsed, err := id.ParseRoleID("role_01h2xcejqtf2nbrexx3vqjhp41")
// Check
if roleID.IsNil() { /* zero-value */ }
// Database support: SQL (Value/Scan), BSON (MarshalBSONValue/UnmarshalBSONValue)
```
## Forge Integration
```go
import (
"github.com/xraph/forge"
wardenext "github.com/xraph/warden/extension"
wardenmw "github.com/xraph/warden/middleware"
)
// Register as Forge extension.
app := forge.New(
forge.WithExtensions(
wardenext.New(
wardenext.WithStore(store),
),
),
)
// Use middleware for route protection.
router.GET("/documents/:id", handler,
forge.WithMiddleware(wardenmw.Require(eng, "read", "document")),
)
```
## Examples
See the `_examples/` directory:
- `_examples/standalone/` — Warden without Forge
- `_examples/forge/` — Warden as Forge extension
- `_examples/rbac/` — Pure RBAC with role inheritance
- `_examples/rebac/` — Zanzibar-style ReBAC
- `_examples/abac/` — Attribute-based policies
## License
Part of the Forge ecosystem.