Introduction
SpiceDB is a database focused on authorization and access control, designed to implement fine-grained authorization (FGA) at scale. In this article, “SpiceDB in Action,” you will learn how to use SpiceDB by building a simple full-stack application that takes advantage of its robust access control capabilities.
Specifically, the journey begins with spinning up SpiceDB locally. You will then create a basic schema and develop a small full-stack app that demonstrates how to effectively use SpiceDB’s access control features. As you progress, you’ll gain insights into key SpiceDB concepts, and by the end of this article, you will have a solid understanding of how to incorporate SpiceDB into your projects.
Fine-Grained Authorization Explained
Fine-grained authorization (FGA) refers to a detailed level of access control where permissions are defined at a more granular level compared to other access control strategies like RBAC and ABAC. Unlike traditional RBAC, where permissions are assigned based on roles, FGA allows for more nuanced and specific access control policies. This is particularly useful in complex systems where different users or groups may need varying levels of access to different parts of the system.
Compared to ABAC, FGA is more flexible and easier to manage. ABAC relies on attributes to define access control policies, which can become cumbersome to handle as the number of attributes grows. FGA, on the other hand, allows you to define permissions directly on resources, making it easier to understand and manage access control policies.
In the context of SpiceDB, FGA is central to its design. SpiceDB allows you to define permissions at the resource level, enabling you to specify exactly who can do what with each resource. For instance, in a document management system, you might want to allow certain users to view a document, others to edit it, and only a select few to delete it. With FGA, these permissions can be precisely defined and enforced.
FGA is particularly powerful when dealing with dynamic or hierarchical permissions. For example, in a multi-tenant application, you might need to define permissions that vary not only by user but also by the organization they belong to, the specific project they’re working on, or even the status of the data they’re accessing. SpiceDB’s support for FGA allows you to model these complex scenarios with ease.
By adopting FGA, you gain the flexibility to enforce security policies that match the specific needs of your application, ensuring that users have the right level of access to the right resources at the right time.
Pre-requisites
To be able to follow along with this tutorial, you’ll need to have the following installed on your machine:
Make sure you have these tools installed before proceeding.
Setting Up SpiceDB Locally
To begin using SpiceDB, the first step is to run it locally using Docker. To ensure consistency and avoid potential issues with future versions, the Docker image will be pinned to a specific version, v1.35.3
. This approach ensures that the instructions provided remain relevant even if new versions of SpiceDB are released.
The following command will start a SpiceDB instance:
1 | docker run --rm -p 50051:50051 authzed/spicedb:v1.35.3 serve --grpc-preshared-key "some-random-key-here" |
This command starts SpiceDB and makes it accessible on port 50051
. The --grpc-preshared-key
flag is used to set a simple pre-shared key for gRPC communication. For the purposes of this tutorial, a fixed key is used for simplicity. In production, this key would typically be managed more securely.
Validating the Setup
Now that SpiceDB is running locally, you can validate the setup by using the SpiceDB CLI to interact with the server. First, set up a context for the local server:
1 | zed context set local localhost:50051 "some-random-key-here" --insecure |
Note: Contexts in SpiceDB are used to manage connections to different servers. In this case, a context named
local
(an arbitrary name) is being set up to point to the local server running onlocalhost:50051
.
After creating the context, list the available contexts to ensure that the local context is set correctly:
1 | zed context list |
The command above should output something similar to the following:
1 | CURRENT NAME ENDPOINT TOKEN TLS CERT |
Next, run a simple command to ensure that the server is responding correctly:
1 | zed version |
This command will return the version of the CLI along with the version of the SpiceDB server you are running, confirming that the setup is correct:
1 | client: zed v0.21.0 |
Creating the First SpiceDB Schema
With SpiceDB set up and running locally, the next step is to create an initial schema that will represent the foundation of the permissions system. In this section, you will define a basic schema, focusing on the core concepts of objects, relationships, and permissions.
Schema Overview
The schema you’ll create represents the core components of a task management application. In this application, users will be allowed to create, view, edit, and also delete their own tasks. To manage these interactions, you will define two main object types: user
and task
. The user
object represents the users of the system, while the task
object represents the tasks that users can interact with. Additionally, the schema will define three types of permissions: view
, edit
, and delete
.
Note: you won’t map the
create
permission just yet because there is a slight difference in how it is handled in SpiceDB compared to the other permissions. You will revisit this later in the tutorial.
Schema Definition
Here’s the SpiceDB schema that defines the user
and task
objects, along with the relationships and permissions:
1 | definition user {} |
Save the contents of the snippet above in a local file named task-manager-schema.zed
. After that, run the following command to add it to SliceDB:
1 | zed schema write task-manager-schema.zed |
To make sure the schema was successfully written, you can read the schema back using the following command:
1 | zed schema read |
SpiceDB Concept Definitions
In SpiceDB, authorization is modeled using three core concepts: objects, relationships, and permissions. These concepts work together to define who can do what within your application.
Objects
Objects are the fundamental entities within SpiceDB. They represent the resources or entities in your system that need to be protected. An object can be anything from a user, a document, or, in this case, a task.
- Example in Schema: In the task manager schema, the
user
andtask
objects are defined. Theuser
object represents the users of the system, while thetask
object represents the tasks that users can interact with.
Relationships
Relationships define how objects are connected within SpiceDB. They establish links between objects and are used to determine access rights. A relationship typically connects an object to a user or another object, indicating a specific type of association, such as ownership or membership.
- Example in Schema: In the task manager schema, relationships like
owner
,editor
, andviewer
are defined within thetask
object. Theowner
relationship, for instance, links atask
to auser
, indicating that theuser
owns thetask
and, therefore, has permissions over it.
Permissions
Permissions in SpiceDB are rules that define what actions can be performed on objects based on the relationships. Permissions are typically defined by combining relationships, allowing for flexible and powerful access control policies.
- Example in Schema: In the task manager schema, the
view
,edit
, anddelete
permissions are defined. Theview
permission is granted to users who are eitherowners
orviewers
of atask
, while theedit
permission is granted toowners
oreditors
. Thedelete
permission is reserved solely for theowner
.
Creating Relationship Instances
With the schema in place, the next step is to create specific relationship instances. These instances define which users can interact with which tasks, and in what capacity. By creating these relationships, you enforce the access control rules defined in the schema. When developing the application, you will create a few more relationship instances, but for the moment those should get you going.
Using the zed
CLI, you can create relationships between users and tasks as follows:
1 | # task-001 is owned by user-001 |
The above, creates the following relationship instances:
task-001
is owned byuser-001
, editable byuser-002
, and viewable byuser-003
.task-002
is owned byuser-002
.
Apart from the explicit relationships defined above, the schema also implicitly grants permissions based on the relationships. For example, user-001
can view, edit, and delete task-001
because they are the owner. Similarly, user-002
can view and edit task-001
because they are an editor, and user-003
can view task-001
because they are a viewer.
To make sure those permissions are properly set, you can check them using the following commands:
1 | # check if user-001 can view task-002 (should return false) |
The commands above validate the permissions set in the schema. For instance, user-002
can view task-001
because they are an editor, while user-001
cannot view task-002
because they are not the owner.
Integrating SpiceDB with a Node.js
Now that you have SpiceDB up and running and have played a bit with the schema and some relationships, it is time to learn how to integrate it with Node.js. In this section, you will create a simple Node.js script that talks to SpiceDB and checks permissions. The focus will be on the core interaction between the Node.js and SpiceDB, rather than building a full-blown API.
The NPM project
First, create a new directory for the project and navigate into it:
1 | mkdir spicedb-nodejs-demo |
Then, initialize a new NPM project:
1 | npm init -y |
Now, open the directory in your preferred IDE/editor and load the package.json
file. On that file, add "type": "module"
to it. This tells Node.js that the project is using ECMAScript modules.
1 | { |
After creating the new project and configuring it to behave as an ECMAScript module, you can use your terminal to install the @authzed/authzed-node
package, which provides the client library for interacting with SpiceDB:
1 | npm install @authzed/authzed-node |
Script for Checking Permissions
With that in place, create a src
directory in your NPM project and an check-permission.js
file inside it:
1 | mkdir src |
Then, open the src/check-permission.js
file and add the following code:
1 | import { v1 } from '@authzed/authzed-node'; |
Here’s a breakdown of what this code does:
- It imports the SpiceDB client library.
- It creates a new client, connecting to the local SpiceDB server. You should replace
some-random-key-here
in case you used a different key when starting the server. - It uses the
checkPermission
method to ask SpiceDB ifuser-001
has permission toview
thetask-001
. - The callback-based
checkPermission
method is wrapped in a Promise. This allows the use ofasync
/await
syntax, which is more convenient to work with. - It then checks the
permissionship
in the response. This indicates whether the user has permission or not. - Finally, it logs a message based on the result. If something goes wrong, it catches the error and logs it.
To run this script, you can use the following command:
1 | node src/check-permission.js |
If everything is set up correctly, you should see a message indicating whether user-001
has permission to view task-001
. If the permission is granted, the message should say “You do have permission. Go ahead!”.
Script For Bulk Checking Permissions
Another important feature of SpiceDB is the ability to check permissions in bulk. This is useful when you need to check permissions for multiple users or resources at once without making individual requests for each one. In this section, you will see how to use the bulk permission check feature in SpiceDB.
To do that, create a new file named bulk-check-permissions.js
inside the src
directory and add the following code:
1 | import { v1 } from '@authzed/authzed-node'; |
After that, you can run the script using the following command:
1 | node src/bulk-check-permissions.js |
If things work as expected, you should see an output like this:
1 | user-001 has permission for task-001. Tell them to proceed! |
This output indicates that user-001
has permission to view task-001
but not task-002
.
The code shouldn’t be too hard to understand, but one thing worth mentioning is the response.pairs.map
method. This method iterates over the pairs of requests and responses, allowing you to process each one individually.
On the first script, where you checked for a single permission, you didn’t have to care about extracting resource and subject information from the response because, well, there was only one. But when checking permissions in bulk, you need to process each pair individually to get the relevant information.
Script for Writing Relationships
Another important feature of SpiceDB is the ability to write relationships. This is useful when you need to create new relationships between objects. In this section, you will see how to use the relationship write feature in SpiceDB.
For starters, create a new file named write-relationship.js
inside the src
directory and add the following code:
1 | import { v1 } from '@authzed/authzed-node'; |
After that, you can run the script using the following command:
1 | node src/write-relationship.js |
If everything goes well, you should see a message indicating that the relationship was created successfully. Now, as an exercise, you can change either the check-permission.js or bulk-check-permissions.js scripts to include the new relationship and check if the permissions are set correctly.