Evaluates a permission check against a SpiceDB
(or any Authzed-compatible) relationship graph. A single
CheckPermission RPC per request determines whether the authenticated
subject has the specified relationship on the target resource.
Source: pkg/authz/spicedb — registered as spicedb.
- Your access model is relationship-based — "user X is a member of org Y, which owns document Z".
- You already have (or plan to deploy) SpiceDB / Authzed for fine-grained permissions.
- You need consistent permission checks across multiple services sharing a SpiceDB schema.
Don't use for simple role checks (rbac), expression-based
rules (cel), or graph models hosted on OpenFGA
(openfga).
All five check.* fields are Go text/template strings, not plain
values — resourceType/permission/subjectType included, not just
resourceId/subjectId. Field names use lowercase-d Id
(resourceId, subjectId), not resourceID/subjectID. There is
no tls: block — TLS uses the system CA pool unconditionally
(grpcutil.WithSystemCerts); there's no way to pin a custom CA file
today.
authorizers:
- name: spicedb-check
type: spicedb
config:
endpoint: "spicedb.authz.svc:50051"
token: "${SPICEDB_PRESHARED_KEY}" # see warning below
insecure: false # set true only for local dev
timeout: "200ms"
consistency: "minimize_latency" # or "fully_consistent" — no third option
check:
resourceType: "document"
resourceId: "{{ index .Request.PathParts 1 }}"
permission: "{{ .Request.Method | lower }}"
subjectType: "user"
subjectId: "{{ .Identity.Subject }}"| Field | Type | Default | Description |
|---|---|---|---|
endpoint |
string | required | SpiceDB gRPC address (host:port) |
token |
string | required | Pre-shared key or bearer token |
insecure |
bool | false |
Allow plaintext (non-TLS) connection |
timeout |
duration | 200ms (default 2s if omitted) |
Per-RPC deadline |
consistency |
string | "minimize_latency" |
minimize_latency or fully_consistent — any other value fails config validation |
check.resourceType |
template | required | Object type in the schema |
check.resourceId |
template | required | Resolves to the resource's object ID |
check.permission |
template | required | Relation/permission to check |
check.subjectType |
template | required | Subject object type |
check.subjectId |
template | required | Resolves to the subject's object ID |
!!! warning "token is read literally — no ${VAR} substitution"
lwauth does not expand ${SPICEDB_PRESHARED_KEY}-style
placeholders anywhere in AuthConfig. Two ways to actually inject
it: token: "vault://kv/lwauth/spicedb#token" (resolved at
compile time — any string field in a module's config: is
checked recursively, internal/config/loader.go's
resolveMapSecrets), or template the AuthConfig YAML itself at
the deployment-pipeline layer (Helm, Kustomize, CI) so the real
token is already inlined before lwauth ever parses it.
Templates in the check block receive {Identity, Request}
(templateInput in spicedb.go) — there is no .Request.PathSegment N, .Request.Header "...", or .Request.Query "..." method; use
indexing/field access instead:
| Field/function | Description |
|---|---|
.Request.Method |
HTTP method |
.Request.Host |
Host header |
.Request.Path |
Full path |
.Request.PathParts |
[]string, path split on / with leading empty segment stripped — index into it, e.g. index .Request.PathParts 1 |
.Request.TenantID |
Tenant ID |
.Request.Headers |
map[string]string, lowercased keys, first value only |
.Identity.Subject |
Authenticated subject |
.Identity.Claims |
Map of identity claims |
.Identity.Source |
Identifier that matched |
lower / upper / sanitize |
Only three helper functions available, e.g. {{ .Request.Method | lower }} (sanitize restricts a string to SpiceDB-safe object-ID characters) |
# values.yaml
config:
inline: |
authorizers:
- name: spicedb-check
type: spicedb
config:
endpoint: "spicedb.authz.svc:50051"
token: "vault://kv/lwauth/spicedb#token"
check:
resourceType: document
resourceId: "{{ index .Request.PathParts 1 }}"
permission: view
subjectType: user
subjectId: "{{ .Identity.Subject }}"- Latency. The
CheckPermissionRPC typically returns in <5ms when SpiceDB has the relationship cached. Settimeoutto at least 2× your p99 to avoid false denies during GC pauses. - Consistency.
minimize_latencyuses SpiceDB's cache (eventually consistent). Switch tofully_consistentfor writes that must reflect immediately (at a latency cost). - Caching. The pipeline's decision cache also caches SpiceDB allow
results. Configure
cache.ttlon the engine to control freshness. - Circuit breaker. Uses the shared
upstream.Guardso SpiceDB outages trigger the circuit breaker rather than queuing requests.
- DESIGN: DESIGN.md.
- Source: pkg/authz/spicedb/spicedb.go.
- Tests: pkg/authz/spicedb/spicedb_test.go.