ZedTokens & Zookies
Overview
What is a ZedToken?
A ZedToken is a token representing a point-in-time of the SpiceDB datastore, encoded for easy storage and transmission.
Why should I use them?
In SpiceDB, there is a requirement for both proper consistency, as well as excellent performance.
To achieve performance, SpiceDB implements a number of levels of caching, to ensure that repeated permissions checks do not need to be recomputed over and over again, so long as the underlying relationships behind those permissions have not changed.
However, all caches suffer from the risk of becoming stale: if a relationship has changed, and all the caches have not been updated or cleared, there is a risk of returning incorrect permission information.
This problem is known as the New Enemy Problem: a permission for a user could be removed, sensitive information then added into the protected resource, and if the permissions check for that user had been previously cached as Affirmative
, then the user could incorrectly be granted access to sensitive information.
To work around this problem, while still ensuring excellent performance, the API uses ZedTokens: a token that specifies that caches must have data at least as fresh as this token, or else they cannot be used.
How do I use them?
A ZedToken is typically used by first issuing a authzed.api.v1.CheckPermission
or authzed.api.v1.WriteRelationships
call.
The API call will return a ZedToken, which the caller then stores alongside the resource (e.g. in an additional column in a relational database).
On the next permissions check for the resource, the ZedToken is provided to SpiceDB, ensuring that the check is working with data as fresh as the previous request.
v1 API
In the v1 API, a ZedToken is returned by all API calls, representing the point-in-time at which the API's result was computed.
When changing the content of a resource
- Issue an
authzed.api.v1.CheckPermission
request for the resource, with Consistency offully_consistent
to ensure the current user still has access - The ZedToken is found in the
checked_at
field in the Check response. - The ZedToken is stored next to the resource, in the database.
When adding or removing a relationship for a resource
- Issue an
authzed.api.v1.WriteRelationships
request for the resource, to add or remove a relationship permitting access to that resource, for a subject. - The ZedToken is found in the
written_at
field in the Write response. - The ZedToken is stored alongside the resource wherever that may be.
When adding or removing a resource
- Issue an
authzed.api.v1.WriteRelationships
request for the resource and its parent resource, indicating that the resource is now linked to its parent resource. - The ZedToken is found in the
written_at
field in the Write response. - The ZedToken is stored alongside the parent resource.
- Use the ZedToken for future
authzed.api.v1.LookupResources
requests.
Using the stored ZedToken
All CheckPermission
calls are given the stored ZedToken via the Consistency configuration:
// If ZedToken is present
CheckPermissionRequest{
consistency: {
at_least_as_fresh: ZedToken{
token: `stored zookie`
}
}
}
// Otherwise, use full consistency
CheckPermissionRequest{
consistency: {
fully_consistent: true
}
}
Frequently Asked Questions
If I store ZedTokens will they become stale at some point?
In v1, the recommendation is to use at_least_as_fresh
in the Consistency option passed to your CheckPermission
call, ensuring that it defines merely a lower bound for staleness.
In v0, Zookies specify lower bound for the time at which caches are considered valid (except for Read
), so any information newer than that bound will be checked as needed.
How do I check a permission at a specific point in time?
In v1, you can pass the ZedToken as at_exact_snapshot
in the Consistency options to ensure the API result is computed at that exact point.
SpiceDB will expire garbage and collect it over time.
The above API call can fail with a "Snapshot Expired" error.
It is recommended to only use this option if you are paginating over results within a short window.
This window is determined by the --datastore-gc-window
when running SpiceDB.
How should I interact with Zedtokens when I'm modifying content in my application?
The recommendation (see above) is to issue an authzed.api.v1.CheckPermission
request for the resource being modified with Consistency of fully_consistent
before sensitive information is saved, storing the resulting ZedToken along with the sensitive information being written.
This ensures that subsequent permission checks that are provided the ZedToken will compute on the state of the permissions system from when the sensitive information was written.
How do I use ZedToken's with authzed.api.v1.LookupResources
?
Since LookupResources
provides the ability to lookup all accessible resources of a particular type, this means we cannot simply use the ZedToken associated with the resources being found.
The recommendation for using a ZedToken with authzed.api.v1.LookupResources
is to use the ZedToken stored for the parent resource of the resources being found.
For example, imagine the following schema:
definition user {}
definition organization {
relation admin: user
}
definition resource {
relation org: organization
relation viewer: user
permission view = viewer + org->admin
}
Since all resources "live" within an organization, the recommendation is to store the ZedToken created when the relationship between the resource
and its parent organization
is written.
By doing so, when the ZedToken is provided to the lookup, the resource is guarenteed to be visible.
It is also recommended to grant the user access to the resource in the same call to WriteRelationships
.
What happens if I lose my ZedToken?
You can always get a new one by issuing any call in v1 (CheckPermission
with Consistency of fully_consistent
is recommended).
What happens if I don't use a ZedToken at all?
The SpiceDB API will default to Consistency of minimize_latency
.
If you do not intend to store ZedTokens, it is recommended that all calls use Consistency of fully_consistent
(which has a major performance hit, but is fully safe). Really, though, you should store ZedTokens.
Only ever using a consistency of minimize_latency
can expose your application up to the New Enemy Problem.
Internal Implementation Details
In SpiceDB, a ZedToken is the v1 equivalent of the v0 Zookie and are, in fact, wire back compatible with Zookies to allow for ease of upgrade.
- Zed Tokens (v1):
- Zookies (v0):
➡️ Proto Definitions for the encoded forms
Zookie vs snapshot tokens
A Zookie typically represents a point in time minimum for an API call: if there is newer information available, it will be used, as the Zookie is defining a lower bound on the freshness of the information being used to compute a permission request.
This does not apply in the v0 Read
API, where the Zookie is instead used as a snapshot token, to represent a single point in time that should be read.
In v1, this is all mitigated by the requirement to explicitly specify behavior via the Consistency block on all API requests.
Internal Representation in SpiceDB
A ZedToken consists of a single token
field:
ZedToken {
Token: "encoded-proto-here"
}
which itself is the encoded form of a Protocol Buffer
Within the encoded ZedToken is the revision
, matching a revision from the configured datastore.
Similarly, a Zookie consists of a token
field, storing an encoded Zookie Protocol buffer.
ZedTokens and Zookies are not portable across datastore implementations.