In this guide to Next.js authorization, we’ll learn about authorization, how it works, and how to integrate it into a Next.js application using Cerbos, an open-source authorization project. This approach will allow us to seamlessly manage and enforce access controls within our web application. Let's get started.
Authorization grants or denies access to resources based on user type, setting rules for user actions within the app. Authorization defines the criteria for what actions a user can carry out within an application. For instance, consider a blogging application where a user attempts to edit a blog. The authorization mechanism checks if the user is the owner of that particular blog post. If the user is not the owner, access to edit the blog is denied, ensuring that users can only edit their own blog posts.
Access control is crucial for managing authorization within applications, particularly in ensuring that only authorized users can access certain functionalities. In the context of our blogging application, which consists of five users (User1, User2, User3, User4, and User5), access control helps define and enforce who can edit or delete blog posts.
In our application, we want to implement a specific access control policy:
Cerbos, as a stand-alone open-source project, streamlines access control by centralizing policy decisions and audit log collection, eliminating the need for replicating rules across components. SDKs for the most popular programming languages and a straightforward API for integrating with any other software make it flexible and adaptable. Cerbos can function effectively within diverse software environments, including decentralized architectures. And it has multiple deployment options, including a Kubernetes sidecar and a Docker container, which allow it to scale seamlessly with other services.
In our blogging platform built in Next.js, we have five users, and we want to give admin permission to user2 and user5, giving them the authority to update and delete all the blogs irrespective of the author. As of now, the way our blogging applications work is that nobody can edit or delete anyone else's posts. We need some policy or authorization mechanism. Below is a typical example of how our permission should look:
Resource policies govern the actions permitted or forbidden on application entities known as resources. In a project management tool, for example, this could mean defining who can edit a particular task or who can view the entire project plan.
Some identity providers (e.g., Okta, Auth0) also provide basic user roles. Derived roles enhance the IdP-provided roles by incorporating context-specific information for more granular access control. For instance, a general "team_lead" role could be converted to "team_lead_finance" by augmenting the role with logic considering the departmental context. This new role could then be granted specific access to the relevant finance department’s documents and not those of other departments.
Cerbos Hub is a centralized repository or platform designed to streamline the management and sharing of Cerbos policies. It acts as a collaborative space where developer, product, and security teams can access a variety of pre-built policy templates, develop their own custom policies, and utilize best practices for access control configuration. Cerbos Hub manages the validation, testing, compilation, and deployment of policy updates across all connected Cerbos policy decision points. As part of the continuous integration process, embedded bundles are also released, and clients can access the most recent versions through a specific URL.
Cerbos PDP, or Policy Decision Point, is the core component of the Cerbos system that evaluates incoming access requests against defined policies to make authorization decisions. When an application queries the PDP with details about a user's action on a resource, the PDP assesses this request in the context of the applicable policies and returns a decision—allow or deny—based on the rules specified. The PDP ensures that access control logic is centralized, decoupling it from application code to enhance security, maintainability, and scalability.
We suggest installing the dependencies with the following minimum versions to ensure compatibility with this tutorial. If your dependencies are not aligned with those we’ve mentioned, consider upgrading them now.
First things first, we need to build the next.js application before implementing the authorization. We will continue with our blogging example.
File Structure of Next.js application
.
├── app
├── cerbos
├── data
├── lib
├── node_modules
├── public
└── utils
To get started, clone this repository and follow along. Ensure you are in the root directory, in this case it’s /nextjs-cerbos.
Now run _npm install_
to install dependencies.
To bootstrap or initialize the application, run the _npm run dev_
in the root directory.
Once the build is successful, you should see the web page loading on your browser at localhost:3000.
To integrate Cerbos in our Next.js application, we will begin by launching the Cerbos Docker container.
In the root directory (/nextjs-cerbos) use the following command to run the Docker container:
docker run --rm --name cerbos -d \
-v $(pwd)/cerbos/policies:/policies \
-p 3592:3592 -p 3593:3593 \
ghcr.io/cerbos/cerbos:latest
Run docker ps to ensure the container persists.
Next, we are using the @cerbos/grpc library to initialize the cerbos client. It is a client library for interacting with the Cerbos Policy Decision Point service over gRPC.
import { GRPC as Cerbos } from "@cerbos/grpc";
export const cerbos = new Cerbos("localhost:3593", { tls: false });
A policy file dictates resource management and access in applications, outlining permissible actions based on user roles to ensure secure and flexible access control.
Now, we need to define the policies governing access control according to the needs of the application.
Write your RBAC (Role-Based Access Control) policy with a few clicks using Cerbos RBAC Policy Generator — it's easy to use, developer-friendly, and hassle-free to generate policies.
To create a policy file, we will include all our blogs as resources and checkboxes based on our application needs. After making our selections based on logic, we can click the Generate button to generate a YAML policy file named basicResource.yaml.
With the RBAC policy generator, in addition to generating the policy file, there is also an interface where you can select the principal and resource to check permissions based on the policy file.
Should the user be User 1, the owner, or Users 2 or 4 with administrative privileges, access to all actions would be rightfully authorized.
Cerbos RBAC policy generator takes care of the syntax, while you can focus solely on the logical aspect of the policies.
The RBAC Policy Generator can be used to create drafts of your policies or even full-fledged policies if the logic incorporates the needs of your application. However, for this article we also created custom policies for our next.js blogging application.
We have depicted a basic user-based resource policy in our documentation. You can refer to it and modify it as per your business policies.
To effectively manage the authorization logic, we can organize the policies into separate files: one for outlining derived roles, another for specifying principal policies when a user requires special access, and a third for detailing the main policies required by the application. These are considered best practices. For guidance on writing these policies, you can refer to examples at play.cerbos.dev.
The policies directory under cerbos in the next.js folder structure (path: /nextjs-cerbos/cerbos/policies ) has YAML files that take care of the policy configurations: (blog_post_roles.yaml) determines the derived roles, and (blog_policy.yaml) defines the access control based on these roles.
blog_post_roles.yaml
This policy grants users a derived role of "owner" if their ID matches the owner attribute of a blog post, thereby simplifying the declaration of complex access control logic.
apiVersion: api.cerbos.dev/v1
derivedRoles:
name: BlogPostRoles
definitions:
- name: Owner
parentRoles: ["user"]
condition:
match:
expr: request.resource.attr.owner == request.principal.id
blog_policy.yaml
This policy document outlines access rules for a "blog" resource. It grants reading privileges to a role admin and a derived role owner, ensuring broad visibility. Admins are given full capabilities to modify or delete any blog post, signifying complete content oversight. For users, update and delete permissions are conditioned on ownership—users can only alter posts they own, preventing unauthorized modifications to others' content.
apiVersion: api.cerbos.dev/v1
resourcePolicy:
version: default
importDerivedRoles:
- BlogPostRoles
resource: 'blog'
rules:
- actions: ["*"]
effect: EFFECT_ALLOW
roles:
- admin
- actions: ["update", "delete", "read"]
effect: EFFECT_ALLOW
derivedRoles:
- owner
In this scenario, Cerbos functions as the Policy Decision Point (PDP), where it evaluates incoming requests against predefined policies to make authorization decisions. When a request to perform an action on a blog post is received, the Cerbos PDP initially identifies the relevant resource policy based on the type of resource involved. It then determines if there are any derived roles that need to be evaluated for the specific context of the request.
Following this, Cerbos PDP assesses the applicable rules associated with the action requested. This process ensures that the decision to grant or deny access is made according to the established policies, considering the user's relationship to the resource, such as whether the user owns the post.
The cerbos.checkResource function evaluates if a user (the "principal") has permission to perform specified actions on a resource. It processes the user's ID, roles, and attributes, alongside the resource's type, ID, and attributes, against defined access policies. By listing actions like ["read," "edit," "delete"], the function returns a decision for each, ensuring actions align with user permissions. This mechanism centralizes and simplifies access control, maintaining application security by enforcing policy-based permissions efficiently.
import { NextResponse } from "next/server";
import { cerbos } from "../../../lib/cerbos-client";
import { users } from "@/data/dummy";
import { getBlog, updateBlog, deleteBlog } from "../../../utils/service";
export async function GET(req) {
try {
const blogId = req.nextUrl.searchParams.get("blogId");
const userId = req.nextUrl.searchParams.get("userId");
const blog = await getBlog(blogId);
if (!blog) {
return NextResponse.json({ error: "Blog not found" });
}
const user = { id: users[userId].name, roles: [users[userId].role] };
const actions = ["read"];
const resourceKind = "blog";
const result = await cerbos.checkResource({
principal: {
id: user.id,
roles: user.roles,
attributes: user,
},
And
// Asynchronously checks permissions with Cerbos for a given resource and user.
const result = await cerbos.checkResource({
principal: {
id: user.id, // The unique identifier of the user making the request.
roles: user.roles, // An array of roles assigned to the user (e.g., ["admin", "user"]).
attributes: user, // Additional attributes of the user that might be used in policy decisions.
},
resource: {
kind: resourceKind, // The type of resource being accessed (e.g., "blog").
id: blog.id, // The unique identifier of the resource.
attributes: blog, // Additional attributes of the resource that might be used in policy decisions.
},
actions: actions, // An array of actions the user is attempting to perform on the resource (e.g., ["read", "update"]).
});
Now that we've integrated Cerbos authorization and created the policies, now let's run our next.js app using _npm run dev._
Now that the app is running, you can access it by navigating to localhost:3000.
And test whether the app aligns with the defined policies.
To test the efficiency and functionality of our access restriction features, we will attempt to alter a blog document or post without owning it or having administrative access. Our system should detect this unauthorized attempt and block access accordingly.
When user3 attempts to edit blog1, written by user1, the request should be denied, and an alert stating "unauthorized user" should be displayed.
Now let’s try to delete a blog post as the owner or administrator to evaluate the performance of our access control system from a different angle. In this case, our system identifies the user's permitted status and provides access to the desired modifications.
The above demonstration shows that the blog owner can delete their blog.
Let us take a deep-dive into what actually is happening under the hood:
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.