Relationships
In SpiceDB, a functioning Permissions System is the combination of Schema, which defines the structure of data, and Relationships, which are the data.
Understanding Relationships
Let's start with a simple schema that models a document sharing system:
definition user {}
definition team {
relation member: user
}
definition document {
# both specific users and all members of specific teams can edit the document
relation editor: user | team#member
}This schema defines three types of Objects: user, team and document. The document type has one relation defined on it: editor.
A relation is like a class definition in Object-Oriented programming or a type in a strongly-typed language: it represents a possible type of connection defined in your schema. For example: "documents have editors".
A relationship is a specific instance of a relation - it's the actual data. For example: "user emilia is an editor of document readme"
Relationship Syntax
The syntax used for relationships in the paper that popularized ReBAC and that we use throughout this website is:
document:readme#editor@user:emiliaLet's break this down:
resource subject
ID type
\ˍˍˍˍˍ\ \ˍˍ\
document:readme#editor@user:emilia
/¯¯¯¯¯¯¯/ /¯¯¯¯¯/ /¯¯¯¯¯/
resource relation subject
type IDThis relationship can be read as: "user emilia is an editor of document readme". Note how this is connecting two specific objects.
We can also write relationships that link one object to a set of objects.
document:readme#editor@team:engineering#memberLet's break this down:
resource subject
ID type
\ˍˍˍˍˍ\ \ˍˍ\
document:readme#editor@team:engineering#member
/¯¯¯¯¯¯¯/ /¯¯¯¯¯/ /¯¯¯¯¯¯¯¯¯¯//¯¯¯¯¯/
resource relation subject subject
type ID relationThis relationship can be read as: "every object that has the member relation to team:engineering is an editor of document readme".
In a real system, Object IDs are most likely a computer-friendly string than something human readable. Many use-cases use UUIDs or unsigned integers representing the primary key from that data's canonical datastore.
Users are no exception to this pattern and can be represented in various ways, such as the sub field of an JWT from an Identity Provider.
Regardless of their representation, Object IDs must be unique and stable within the set of IDs for an Object Type.
Graph traversals
At its core, authorization logic fundamentally reduces to asking:
Is this actor allowed to perform this action on this resource?
For example: "Is user emilia allowed to edit document readme?"
If you had these relationships written in SpiceDB:
document:readme#editor@user:emilia
Then the answer is trivial: yes, emilia can edit the document.
If, instead, you had these relationships written in SpiceDB:
team:engineering#member@user:emilia- emilia is on the engineering teamdocument:readme#editor@team:engineering#member- every member on the engineering team can edit the readme
When checking "Can user emilia edit document readme?", SpiceDB:
- Starts at
document:readme#editor - Follows the
editorrelation to findteam:engineering#member - Follows the
memberrelation to finduser:emilia
Note how we followed a chain of relationships to answer the question. Or, put differently, we traversed a graph (opens in a new tab).
The real power of ReBAC comes from transforming authorization questions into graph reachability (opens in a new tab) problems, and then answering them efficiently:
Is there a chain of relationships starting at this resource and relation that ultimately reaches this subject?
This is what makes relationships powerful: they are both the question you ask ("does this relationship path exist?") and, when you write many of them together, they form the answer (by creating paths through the graph that SpiceDB can traverse).
Writing Relationships
It is the application's responsibility to keep the relationships within SpiceDB up-to-date and reflecting the state of the application; how an application does so can vary based on the specifics of the application, so below we outline a few approaches.
Want to learn more about writing relationships to SpiceDB, the various strategies and their pros and cons?
Read our blog post about writing relationships (opens in a new tab).
SpiceDB-only relationships
Sometimes an application does not even need to store permissions-related relationships in its relational database.
Consider a permissions system that allows for teams of users to be created and used to access a resource. In SpiceDB's schema, this could be represented as:
definition user {}
definition team {
relation member: user
}
definition resource {
relation reader: user | team#member
permission view = reader
}In the above example, the relationship between a resource and its teams, as well as a team and its members does not need to be stored in the application's database at all.
Rather, this information can be stored solely in SpiceDB, and accessed by the application via a ReadRelationships (opens in a new tab) or ExpandPermissionsTree (opens in a new tab) call when necessary.
Two writes & commit
The most common and straightforward way to store relationships in SpiceDB is to use a 2 phase commit-like approach, making use of a transaction from the relational database along with a WriteRelationships (opens in a new tab) call to SpiceDB.
try:
tx = db.transaction()
# Write relationships during a transaction so that it can be aborted on exception
resp = spicedb_client.WriteRelationships(...)
tx.add(db_models.Document(
id=request.document_id,
owner=user_id,
zedtoken=resp.written_at
))
tx.commit()
except:
# Delete relationships written to SpiceDB and re-raise the exception
tx.abort()
spicedb_client.DeleteRelationships(...)
raiseStreaming commits
Another approach is to stream updates to both a relational database and SpiceDB via a third party streaming system such as Kafka (opens in a new tab), using a pattern known as Command Query Responsibility Segregation (opens in a new tab) (CQRS)
In this design, any updates to the relationships in both databases are published as events to the streaming service, with each event being consumed by a system which performs the updates in both the database and in SpiceDB.
Asynchronous Updates
Before adopting an asynchronous system, you should deeply consider the consistency implications.
If an application does not require up-to-the-second consistent permissions checking, and some replication lag in permissions checking is acceptable, then asynchronous updates of the relationships in SpiceDB can be used.
In this design, a synchronization process, typically running in the background, is used to write relationships to SpiceDB in reaction to any changes that occur in the primary relational database.