Authorization and access control in Express
When building web apps, one of the important factors you should consider is how users will access various parts of your app. It is best practice to manage user permissions and access by restricting or granting access to certain application areas. In this post, we'll look at how to use Cerbos PDP (Policy Decision Point) to establish role-based access control (RBAC) in an Express application.
Getting started with Express and understanding Cerbos PDP (Policy Decision Point)
If you're familiar with Node.js, you've probably heard of Express. Many engineers opt to use Express, a Node.js framework, for developing JavaScript web apps. Cerbos PDP can be described as an engine for authorizing access to different parts of your app based on certain rules and policies you set.
Introduction to Role-Based Access Control (RBAC)
RBAC is all about assigning permissions to specific roles rather than individual users. Once roles are defined, users are assigned to these roles based on their responsibilities.
Understanding roles, permissions, and users
Let's say for example, you are building a learning management system. This will call for properly defined roles in your web app.
- Roles: Representing a users function within a system. In our LMS example, to define a role you need to identify all the expected roles in your app E.g super admin, admin, users.
- Permissions: Now that you have well defined roles, you have to grant specific privileges to the roles. For example only a super admin can upload and delete videos.
- Users: Individuals on the app who are assigned predefined roles based on their specific responsibilities.
How-To guide: Practical RBAC implementation in Express with Cerbos PDP
Following our LMS example, we are going to build a sample project to demonstrate the implementation of RBAC in Express with Cerbos PDP.
Development environment setup
Before we begin, there are a few prerequisites you need to have in place. Please ensure that Node.js, Express, and most importantly, Cerbos PDP are installed on your system.
Node.js and Express
Install Node.js for your platform (we recommend LTS) and verify the installation by running the following command in your terminal:
node -v
Once Node.js is installed, install Express as well:
npm install -g express
Cerbos
Install the Cerbos PDP for your platform, and ensure that the binary is in your $PATH (or run it from the extracted location):
Once installed, test it to make sure that it can execute:
cerbos --version
Sample LMS to demonstrate RBAC
Once you're confident that Node.js, Express, and Cerbos PDP are installed, you can start a new project running these commands
mkdir lms-cerbos
cd lms-cerbos
This will create a new folder called lms-cerbos and navigate into it.
Next, initialize a new Node.js project:
npm init -y
Install all required packages
Install the necessary packages, including TypeScript and Axios:
npm install axios dotenv
npm install --save-dev typescript ts-node @types/node @types/express
Initialize a TypeScript configuration file:
npx tsc --init
Cerbos PDP setup
Configure the Cerbos PDP by creating a basic policy file that defines roles and permissions. Create a cerbos directory and add a policies sub-directory.
Create a policy file resource.courses.yaml in the policies/ directory:
# policies/policy.yaml
resource: "course"
version: "default"
roles:
- id: "super-admin"
grants:
- actions: ["create", "upload", "delete"]
condition:
match:
resource: "course"
- id: "admin"
grants:
- actions: ["update", "edit"]
condition:
match:
resource: "course"
- id: "student"
grants:
- actions: ["view"]
condition:
match:
resource: "course"
Roles and permissions
Create a roles.ts file to define the roles and their permissions:
// src/roles.ts
export const roles = {
"super-admin": ["create", "upload", "delete"],
"admin": ["update", "edit"],
"student": ["view"],
};
Routing
Now we can set up routing in Express, where we enforce RBAC using Cerbos.
// src/index.ts
import express, { Request, Response } from "express";
import axios from "axios";
import dotenv from "dotenv";
import { roles } from "./roles";
dotenv.config();
const app = express();
app.use(express.json());
const cerbosURL = process.env.CERBOS_URL || "http://localhost:3592/api/check";
interface User {
id: string;
roles: string[];
}
app.post("/course_action", async (req: Request, res: Response) => {
const { user, action } = req.body as { user: User; action: string };
const cerbosRequest = {
requestId: "1",
actions: [action],
resource: {
kind: "course",
attributes: {
owner: user.id,
},
},
principal: {
id: user.id,
roles: user.roles,
},
};
try {
const response = await axios.post(cerbosURL, cerbosRequest);
const isAllowed = response.data.result[0].actions[action]?.isAllowed;
if (isAllowed) {
res.status(200).send("Action permitted");
} else {
res.status(403).send("Action forbidden");
}
} catch (error) {
console.error("Error communicating with Cerbos PDP:", error);
res.status(500).send("Internal server error");
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Testing out our sample app
Compile and run the application:
npx tsc
node dist/index.js
π‘ You can test the application on Postman by sending POST requests to the /course_action endpoint with different roles and actions.
Sample request:
{
"user": {
"id": "user1",
"roles": ["admin"]
},
"action": "edit"
}
Expected Responses:
- 200 OK if the action permitted.
- 403 Forbidden if the action is forbidden.
Benefits of using Cerbos for Express authorization
Once the integration is in, the reasons to keep authorization out of your route code become clear.
The rules are decoupled from the app. All the logic that used to be spread across handlers lives in one set of policy files, so the full picture of who can do what is readable in one place instead of reconstructed from a grep. Those policies are human-readable YAML with CEL conditions, which means product and security can review and even change them without reading your Express code.
Changing a rule does not require a redeploy. The PDP hot-loads policy files, so updating a YAML file, or pushing to Git with Cerbos Hub, takes effect without restarting your Node process. That alone removes a whole class of "we need a release to flip a permission" tickets.
RBAC and ABAC live together. A single policy can match on static roles and evaluate contextual attributes in the same rule, so you can start with roles and add ownership, department, or time conditions as requirements grow, without swapping approaches.
You get an audit log for free. Every decision can be recorded with the full principal, resource, and outcome, backed by a local file, Kafka, or Hub, and each response carries a call id you can correlate with your own logs. Building that yourself is real work you now skip.
List filtering is handled by query plans rather than per-row checks, so data-heavy endpoints stay fast. The PDP is language-agnostic, exposing REST and gRPC, so the same policies serve your Express app and any other service in your stack. It is open source and Apache-2.0, free to self-host with a docker run, and it is stateless, so you scale it like any other stateless service, as a sidecar per service or a shared deployment behind a load balancer.
Next steps
From this article, you've learned the importance of roles and permissions. You can also see in our example how we used Cerbos to enforce RBAC for an LMS. The super-admin, admin, and student roles are defined in the Cerbos policy file with their respective permissions for the course resource. The Express app communicates with Cerbos to check if a user is authorized to perform a specific action on a course, ensuring that only the appropriate roles can create, upload, delete, update, edit, or view courses.
You now have the whole flow, run the PDP, write a policy, make one call from your route, and layer attributes and list filtering on top as you need them.
Try Cerbos Hub to manage and distribute these policies across environments, or book a call to talk through your Express setup with our team.
Go deeper:
- Why external authorization for the case behind decoupling authorization from your app
- How to adopt externalized authorization (eBook) for a step-by-step playbook
FAQ
Tagged in




