Authorization is the process of verifying the level of access a user or machine has in a software system. It's different from authentication which is the process of verifying that a user belongs to a system. In simpler terms, authentication gets a user into the system, while authorization determines what actions they can perform once inside.
You can implement authorization in your application by writing if-statements to check what actions a user can perform. However, this approach will require you to write many such statements, and ties the authorization logic to your application, so it can't be reused elsewhere. If you need the same rules in another app, you'll end up writing the logic all over again.
To streamline your authorization process and avoid guesswork or unforeseen bugs, you can adopt a central Policy Decision Point (PDP) that all your applications can connect to for authorization checks. Rather than building such a system yourself—which can be time-consuming and error-prone—you can leverage a robust, open-source solution like the one offered by Cerbos. The Cerbos PDP server allows you to define authorization rules as YAML-based policies, which are then processed and exposed via an HTTP REST API. This REST API can then be used directly or, more conveniently, via SDKs provided by Cerbos or the community.
In this tutorial, you will complete the implementation of a blog application that integrates with a Cerbos PDP server for authorization checks. You will add two extra policies: the first will allow users to comment only on published posts and the second will deny users with unverified emails from creating new posts.
To follow along in this tutorial, you need:
Because Cerbos can run as a standalone binary or in a Docker container, the choice between both is based on your personal preference.
NB: This tutorial assumes that you are using a Unix system. If you are on Windows, follow along using Git bash or WSL.
Cerbos is a lightweight authorization service that is deployed within or alongside your application stack. We provide various means of deploying the service to your infrastructure. For example, you can install it as a .deb
or .rpm
package, which gives you a systemd service out of the box. Alternatively, you can download the binary and run it using the process manager that manages your application's startup. If running a container orchestrated environment, we provide an official Docker image. This tutorial provides options for running Cerbos via the binary and Docker.
Before running the Cerbos PDP server via the binary, you must ensure it is present on your PATH. An easy choice for this is the /usr/local/bin
directory. To confirm it is present on your system, run cerbos help
. If you get a help output, then you're clear to proceed.
To run via the binary, cd
into the cerbos
directory within the demo application, and run the cerbos server
command. These steps are illustrated below:
cd cerbos
cerbos server
To run via Docker, cd
into the cerbos
directory and run the docker-start script (bash ./docker-start.sh
). This script contains the docker run
command that mounts the policies in the policies subdirectory (cerbos/policies/
) as a volume and exposes the Cerbos RPC and HTTP APIs on ports 3593 and 3592 respectively. The commands for this step are shown below:
cd cerbos
bash docker-start.sh
Although you already have the demo application source code, you still have not set it up. You must install the required pip dependencies and seed an SQLite database that will contain data on users, posts, and comments. The snippet below contains the commands for creating a Python virtual environment, installing dependencies, and seeding the database. Run it to proceed.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python seed.py
Next, run the application within the sourced environment using the following command:
flask --app app run --debug --port 4600
An SQLite database was created when you executed the database seed command (python seed.py
). This seeding operation adds three uses, six posts, and two comments to the DB. This gives you a soft landing, allowing you to experiment with Cerbos right away without reading through the entire application code. Below is a table listing the seeded users and their roles.
Username | Role | Email Verified | Notes |
---|---|---|---|
andrew | user | Yes | Can create, update, and delete own posts |
bob | user | No | Can create, update, and delete own posts |
alice | admin | Yes | Has unrestricted privileges |
With the available users and their roles defined, you can proceed by using the cURL
requests below to create a post as the user andrew
, and subsequently delete it as the user bob
. The first request returns the details of the newly created post, with the id included. You must copy this id and complete the second request by substituiting <POST_ID>
with it.
# create a blog post as the user andrew
curl -X POST -d '{"title": "16th Century Baking", "body": "16th-century baking did not use sugar"}' -H 'Content-Type: application/json' http://andrew:password123@localhost:4600/api/posts
# sample response
# {
# "message": "Post created successfully",
# "post": {
# "id": 7,
# "published": false,
# "slug": "16th-century-baking",
# "title": "16th Century Baking"
# }
# }
Now attempt to delete the created post:
# delete the blog post as the user `bob`
curl -X DELETE http://bob:password123@localhost:4600/api/posts/<POST_ID>
# Expected response
# {
# "error": "You are not authorized to delete this post"
# }
You'll notice that the basic auth credentials were included directly in the request. Running the second request reveals that the user bob
cannot delete the blog post created by the user andrew
. This behaviour is generally expected, as only the owner of a resource should be able to delete it. The exception to this is an admin principal has has unrestricted privileges. You can delete the blog post using an admin's credentials by running the command below:
curl -X DELETE http://alice:password123@localhost:4600/api/posts/<POST_ID>
# Expected response
# {
# "message": "Post deleted successfully"
# }
The logic preventing the deletion of a post by a different user is represented as code and authorization policy. Within the source code, it is represented as a simple boolean check, like: Is the user bob <principal> allowed to delete <action> post <resource>
. The conditions for this check passing or failing are represented in a YAML file called a policy. The boolean logic preventing deletion can be found in the app/controllers/post_controller.py
file, between lines 149 and 150.
if not check_permission("delete", Post, post):
return jsonify({"error": "You are not authorized to delete this post"}), 403
The corresponding Cerbos policy can be found in the cerbos/policies/post.yaml
---
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: default
resource: post
rules:
- name: allow_all_actions
actions: ["*"]
effect: EFFECT_ALLOW
roles:
- admin
- name: allow_user_to_update_and_delete_posts
actions: ["update", "delete"]
effect: EFFECT_ALLOW
roles:
- user
condition:
match:
expr: request.resource.attr.user_id == request.principal.id
From the definition above, you can see that the admin role, designated in the first roles
field, is allowed to carry out any action (designated by the wildcard *
). The user role is allowed to update and delete posts that match the condition: request.resource.attr.user_id == request.principal.id
. This translates to: post.user_id == user.id
. So users are only allowed to update and delete their own posts.
The admin user's access is governed by a Role-Based Access Control (RBAC) policy, as it is defined solely by role rather than attributes. In contrast, access for the user bob
(when attempting to delete the post created by the user andrew
) is governed by Attribute-Based Access Control (ABAC) because the user_id
attribute of the Post
resource and the principal_id
of the User
resource are used to specify what they can and cannot do.
The Cerbos PDP architecture is compliant with the AuthZEN specification and this introduces concepts like principals (or subjects), resources, and actions—all of which you have interacted with so far.
By definition, a principal is an entity with rules guiding its permissions. This app for example has users defined in the users table as principals. The concept however can extend to bots and service accounts. A resource on the other hand is an entity that a principal interacts with and performs actions on. In the context of this app, resources are posts and comments. A user can also be a resource, but that functionality does not exist in this app.
You can perform auth checks within your code using the is_allowed
method on the client object. It simply checks if a principal is allowed to perform an action on a resource. This check is encapsulated into a function to make it usable across the entire application. You can find it in the function check_permission
in the app/utils/cerbos.py
file. Find the definition below:
def check_permission(action, model, instance):
"""Helper function to check permissions with Cerbos for any model."""
resource = get_resource_from_model(model, instance)
print(resource)
with CerbosClient("localhost:3593") as c:
principal = get_principal(g.user)
return c.is_allowed(action, principal, resource)
From the function definition above, you can see that it requires another function get_resource_from_model
to resolve a model into a Cerbos resource. This function returns the resource object corresponding to the post or comment models. You can think of the post resource being represented in a JSON-esque form. See the snippet below:
Resource(
id="1",
kind="post"
attr={
"user_id": "1"
}
)
Authorization rules can be extended to include very complex definitions. Cerbos allows users to update authorization policies without altering the application's codebase. This means new business conditions can be implemented without modifying a single line of code. Now, without changing any core part of the application, you will create a configuration for allowing comments on posts and conclude the tutorial by defining a policy that prevents users with unverified emails from creating posts.
To allow users to comment only on published posts, add a "create" action rule definition in the cerbos/policies/comment.yaml
file with the yaml snippet defined below:
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: default
resource: comment
rules:
# other rules above
- name: allow_user_to_create_comments
actions: ["create"]
effect: EFFECT_ALLOW
condition:
match:
expr: request.resource.attr.published == true
# other rules below
This policy checks the published attribute of the post to confirm it’s true before allowing the creation of a comment.
Similarly, to restrict users who haven't verified their email from creating posts, update the "create" rule definition in the cerbos/policies/post.yaml
file with the following snippet:
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: default
resource: post
rules:
# othe rules above ...
- name: allow_user_to_create_posts
actions: ["create"]
effect: EFFECT_ALLOW
roles:
- user
condition:
match:
expr: request.principal.attr.email_is_verified == true
# other rules below
This ensures only users with a verified email can create posts.
Multi-user software where different users have different access levels must be designed with fine-grained authorization. Building a robust authorization system that scales beyond a single application takes engineering effort and could lead to security breaches if not properly implemented.
So, what did we learn? This article demonstrated how you can implement Cerbos, a scalable authorization service, into a Flask application. You learnt how Cerbos works via flexible, policy-driven rules. Specifically, you defined rules that allow comments on published posts and deny post creation for unverified users. These rules were defined without modifying the application code indicating that business logic changes can be made without requiring a new deployment. Thus, by implementing Cerbos, you can streamline authorization management in Flask while maintain a secure, flexible system.
If you want to dive deeper into implementing and managing authorization, join one of our engineering demos or check out our in-depth documentation.
Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team
Join thousands of developers | Features and updates | 1x per month | No spam, just goodies.