Warden

Getting Started

Install Warden and run your first authorization check.

Installation

Library

go get github.com/xraph/warden

CLI (warden and warden-lsp)

The warden CLI applies .warden configs to a store; warden-lsp powers the editor extensions. Three options:

go install github.com/xraph/warden/cmd/warden@latest
go install github.com/xraph/warden/cmd/warden-lsp@latest
# Pick a platform from https://github.com/xraph/warden/releases/latest
curl -L -o warden.tar.gz \
  https://github.com/xraph/warden/releases/latest/download/warden-<version>-<os>-<arch>.tar.gz

# Verify the checksum
curl -L -o checksums.txt \
  https://github.com/xraph/warden/releases/latest/download/checksums.txt
shasum -a 256 -c checksums.txt --ignore-missing

tar -xzf warden.tar.gz
sudo mv warden warden-lsp /usr/local/bin/
warden version
git clone https://github.com/xraph/warden.git
cd warden
make build
./bin/warden version

Each pre-built archive contains both warden and warden-lsp. Available for linux, darwin, windows × amd64, arm64.

VS Code extension

code --install-extension xraph.vscode-warden

The extension provides syntax highlighting, completion, hover, go-to-definition, and live diagnostics for .warden files. See the DSL tooling guide for editor configuration.

Step 1: Create a Store

Warden needs a store to persist roles, permissions, policies, relations, and assignments. Start with the in-memory store for development:

import "github.com/xraph/warden/store/memory"

st := memory.New()

Step 2: Create the Engine

import "github.com/xraph/warden"

eng, err := warden.NewEngine(
    warden.WithStore(st),
)
if err != nil {
    log.Fatal(err)
}

Step 3: Create a Role and Permission

import (
    "github.com/xraph/warden/id"
    "github.com/xraph/warden/role"
    "github.com/xraph/warden/permission"
)

// IDs are auto-assigned by the store on Create. The in-place mutation
// means r.ID and perm.ID are populated when the calls return — handy
// for downstream uses like AttachPermission below.

// Create a permission
perm := &permission.Permission{
    Name:     "doc:read",
    Resource: "document",
    Action:   "read",
}
_ = st.CreatePermission(ctx, perm)

// Create a role
r := &role.Role{
    Name: "Viewer",
    Slug: "viewer",
}
_ = st.CreateRole(ctx, r)

// Attach permission to role
_ = st.AttachPermission(ctx, r.ID, permission.Ref{Name: "doc:read"})

Step 4: Assign the Role

import "github.com/xraph/warden/assignment"

ass := &assignment.Assignment{
    RoleID:      r.ID,
    SubjectKind: "user",
    SubjectID:   "user-42",
}
_ = st.CreateAssignment(ctx, ass)

Step 5: Check Authorization

result, err := eng.Check(ctx, &warden.CheckRequest{
    Subject:      warden.Subject{Kind: "user", ID: "user-42"},
    Action:       "read",
    ResourceType: "document",
    ResourceID:   "doc-123",
})

if result.Allowed {
    fmt.Println("Access granted!")
} else {
    fmt.Println("Access denied:", result.Reason)
}

Step 6: Add ABAC Policies (Optional)

import "github.com/xraph/warden/policy"

p := &policy.Policy{
    Name:      "Office Hours Only",
    Effect:    policy.EffectDeny,
    Subjects:  []string{"*"},
    Actions:   []string{"*"},
    Resources: []string{"document:*"},
    Conditions: []policy.Condition{{
        Field:    "time",
        Operator: policy.OpTimeBefore,
        Value:    "09:00",
    }},
    IsActive: true,
}
_ = st.CreatePolicy(ctx, p)

Step 7: Add ReBAC Relations (Optional)

import "github.com/xraph/warden/relation"

tuple := &relation.Tuple{
    ObjectType:  "document",
    ObjectID:    "doc-123",
    Relation:    "viewer",
    SubjectType: "user",
    SubjectID:   "user-42",
}
_ = st.CreateRelation(ctx, tuple)

Step 8: Use with PostgreSQL

For production, switch to the PostgreSQL store:

import (
    "github.com/xraph/grove"
    "github.com/xraph/grove/drivers/pgdriver"
    "github.com/xraph/warden/store/postgres"
)

db, err := grove.Open(pgdriver.New(
    pgdriver.WithDSN("postgres://user:pass@localhost/warden"),
))
if err != nil {
    log.Fatal(err)
}

pgStore := postgres.New(db)
defer pgStore.Close()

// Run migrations
_ = pgStore.Migrate(ctx)

eng, _ := warden.NewEngine(
    warden.WithStore(pgStore),
)

Step 8: Use the Declarative DSL (Optional)

Programmatic seeding works for examples, but production setups define roles, permissions, policies, and relations as source-controlled .warden files.

go install github.com/xraph/warden/cmd/warden@latest
# config/main.warden
warden config 1
tenant t1                              # optional — omit for single-tenant apps

permission "doc:read" (document : read)

role viewer {
  name   = "Viewer"
  grants = ["doc:read"]
}

role editor : viewer {
  name   = "Editor"
  grants += ["doc:write"]
}
warden lint config/                       # static checks, no DB
warden apply -f config/ --store sqlite:./warden.db

The tenant declaration is optional. Single-tenant apps that never call warden.WithTenant should omit it — every entity lands in the global scope (tenant_id = ""), which is what the engine looks up at Check time too.

The same CLI ships a language server (warden lsp) used by the VS Code extension and Neovim/Helix configs for diagnostics, completion, hover, and go-to-definition. See DSL & Tooling.

Or embed the config

If you'd rather ship the config inside your binary instead of distributing files alongside it, use //go:embed and dsl.ApplyFS:

//go:embed all:config
var configFS embed.FS

func main() {
    eng, _ := warden.NewEngine(warden.WithStore(memory.New()))

    _, err := dsl.ApplyFS(ctx, eng, configFS, "config",
        dsl.ApplyOptions{TenantID: "acme"},
        dsl.WithVariables(dsl.Variables{"REGION": os.Getenv("AWS_REGION")}),
    )
    // handle err …
}

Same load-resolve-apply pipeline as the CLI, exposed as a library. Full reference and a runnable example: DSL & Tooling — Programmatic apply.

Next Steps

On this page