Authorization and access control in Flask

Published by Osinachi Chukwujama on February 04, 2025
Authorization and access control in Flask

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.

Prerequisites

To follow along in this tutorial, you need:

  1. Python3 for running the demo application
  2. Cerbos binary
  3. Docker (optional)
  4. The starter source code of the demo application. Clone or download it here.

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.

Running Cerbos

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.

Running Cerbos via the binary

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

Running Cerbos via Docker

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

Setting up the demo application

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

Users and their roles

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

Creating and deleting posts as different users

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"
# }

How Cerbos policies guide authorization

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.

Principals and resources

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"
    }
)

Adding extra authorization logic

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.

Allow users to comment on published 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.

Deny users without a verified email from creating new posts

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.

Conclusion

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