Moving Beyond Roles: Implementing Conditional Access with SpiceDB Caveats
Stop creating hundreds of roles to handle conditional access. Learn how to use SpiceDB Caveats to implement Attribute-Based Access Control (ABAC) for time-bound and context-aware permissions.
11 Dec 2025, 18:53 UTC

The Rigidity of Role-Based Access Control
Role-Based Access Control (RBAC) works well until you encounter a "Yes, but..." requirement. For example: "A user can edit this document, but only if they are on the corporate VPN or "A contractor can access this folder, but only until their contract expires on Friday.”
In a traditional RBAC system, solving this usually leads to "role explosion," where you create hyper‑specific roles like Editor_VPN_Only or Contractor_Expiring_Oct12. This is unmanageable at scale. The solution is Attribute-Based Access Control (ABAC), which allows permissions to be conditional based on real‑time data (attributes) rather than static assignments.
SpiceDB implements this through Caveats. A caveat is a conditional expression attached to a permission that must evaluate to true for access to be granted, using context provided at the moment of the request.
How Caveats Model Conditional Logic
In SpiceDB, the schema defines the relationship between objects, while caveats define the constraints on those relationships. Unlike a standard relation, which is a binary "is or is not," a caveat acts as a filter during the graph traversal process.
When you define a caveat, you are essentially creating a template. This template references attribute names that SpiceDB expects to find in the context object of a CheckPermission call. If the required attributes are missing from the request, or if the expression evaluates to false, the permission is denied for that specific path in the relationship graph.
Worked Example: Time-Bound Access
1. Schema Definition
Run this schema update via the SpiceDB CLI or API. This defines a document that can be viewed by a viewer, provided the time_range caveat is satisfied.
define caveat time_range {
// Expects "now" (current time) and "expiry" (document expiry) in context
expression: now < expiry
}
define object document {
rel viewer: user
permission view = viewer with time_range
}2. Creating the Relationship
Insert a tuple to grant a specific user the viewer role for a document. This is a standard relationship write and does not involve the caveat logic yet.
zed write document:project_alpha#viewer user:alice3. Checking Permission with Context
To verify access, the application must provide the current state of the world in the context field. This is executed via the CheckPermission API call.
Request (Authorized):
Context: {\"now\": 1700000000, \"expiry\": 1700005000}
Result: AUTHORIZED
Request (Unauthorized):
Context: {\"now\": 1700010000, \"expiry\": 1700005000}
Result: UNAUTHORIZED
Performance and Engineering Trade-offs
Caveats shift the burden of state from the database (where tuples are stored) to the request (where context is provided). This has several implications:
- Evaluation Overhead: Every caveat adds a small amount of computational overhead to the permission check. While SpiceDB compiles these into efficient expressions, a single request traversing a deep graph with dozens of caveats will see higher latency than a pure RBAC check.
- Context Responsibility: SpiceDB does not "know" the current time or the user's current IP address. Your application layer is responsible for fetching these attributes and passing them into the
CheckPermissioncall. If your application passes a stale timestamp, SpiceDB will make a decision based on that stale data. - Graph Complexity: Caveats are evaluated during the incremental traversal of the relationship graph. If a user has multiple paths to a resource, SpiceDB only needs one path to evaluate to true (including its caveats) to grant access.
Verifying Your Implementation
To ensure your caveats are behaving as expected, you can use the following verification steps:
- Boundary Testing: Test your context values exactly at the threshold (e.g., if
now == expiry, does it grant or deny?). - Missing Attribute Test: Send a request without the required context keys. SpiceDB should treat missing attributes as a failure to satisfy the caveat, resulting in
UNAUTHORIZED. - Latency Profiling: Compare the response time of a
CheckPermissioncall on a relation without a caveat versus one with a complex caveat to establish your performance baseline.
Rollback and Schema Migration
If a caveat is incorrectly defined, you must update the schema. Because schema changes are applied transactionally, you can remove a caveat by redefining the permission without the with clause. Note that during a rolling update of your SpiceDB cluster, some nodes may briefly evaluate the old caveat while others use the new definition.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.