A complete guide to Next.js authorization

Published by Emre Baran and Rohit Ghumare on April 19, 2024
A complete guide to Next.js authorization

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.

What is Authorization?

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.

What is Access Control?

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:

  • Admin Users: User2 and User5 are designated as admin users. They can edit and delete any blog post within the application, regardless of who the original poster was.
  • Regular Users: User1, User3, and User4 are regular users. These users are restricted to editing or deleting only blog posts that they have created.

Challenges Faced By Developers in Implementing Access Control

  1. Complicated Logic Repetition: Replicating access control logic across different environments, whether cloud-based or on-premises, requires careful coordination by developers. As synchronizing the logic gets more complicated, the likelihood of introducing inconsistencies grows.
  2. Distributed Audit Logs: Maintaining and tracking audit logs becomes challenging in any system architecture due to the spread of logs across multiple systems. This issue creates a disconnect between those who formulate policies and those who implement them, complicating the management of audits and policy updates.
  3. Scalability Issues: As systems grow, scaling access control mechanisms efficiently becomes a critical challenge, highlighting the necessity of balancing security with operational efficiency across all types of systems and patterns.

Where and How Does Cerbos Fit into the Authorization Ecosystem?

Decoupled Authorization

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.

BlogCTA - PDP

Integrating Cerbos in a Next.js Application

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:

  • Admins should have full access to all operations (Role-Based Access Control).
  • Users can view all blogs and manage (edit/delete) only their posts (Attribute-Based Access Control).
  • Read-only access is given to users for blog posts they did not create.

Breakdown of Privileges

image

Types of Policies

Resource Policies

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.

Derived Roles

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.

Policy Creation with Cerbos

Cerbos Hub

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 Policy Decision Point (PDP)

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.

Prerequisites for integrating Cerbos with Next.js

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.

  1. WSL (if Windows is your primary OS)
  2. Docker
  3. Node.js version minimum requirement: 18

Getting started

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

File Structure of our Next.js Application

  • app/: Holds UI components and api routes, including pages and interfaces for our blog.
  • cerbos/: Dedicated to Cerbos authorization policies for access control.
  • data/: Contains blog post data or database connections.
  • lib/: Shared libraries or utility functions for application-wide use.
  • node_modules/: Stores external libraries.
  • public/: For static assets like images, CSS, and client-side JS.
  • utils/: Utility functions for specific tasks within the app.

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.

image

To bootstrap or initialize the application, run the _npm run dev_in the root directory.

image

Once the build is successful, you should see the web page loading on your browser at localhost:3000.

image

Integrating Cerbos as a service

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.

Connecting Cerbos with the Next.js app

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

Generate Policy Files with Cerbos RBAC Policy Generator

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.

image

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.

image

image

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.

  • Principal: Who is performing the action, in our case it could be user1, user3 etc.
  • Resource: What is being accessed, in our case it could be blog1, blog2 etc.

image

Should the user be User 1, the owner, or Users 2 or 4 with administrative privileges, access to all actions would be rightfully authorized.

image

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.

image

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._

image

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.

Testing authorization in a Next.JS application

image

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.

image

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.

image

image

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:

  • A user (the principal) attempts to delete (action) a blog post (resource).
  • The Cerbos PDP receives the request and evaluates it based on the defined policy.
  • The PDP checks if the blogPost resource's owner attribute matches the currentUser's id.
  • If the condition is true, a derived role of “owner” is given to the user, and based on the role, action is either allowed or denied. The test successfully proved the viability of Cerbos' access control, enabling safe and efficient content and identity management for authorized users and consumers.

FAQ

How can attribute-based access control (ABAC) be utilized in Next.js for dynamic authorization?

How do you implement access control for static and dynamic pages in Next.js?

How do you implement Role-Based Access Control (RBAC) in Next.js applications?

What is Next.js authorization?

How does Next.js handle authorization?

Book a free Policy Workshop to discuss your requirements and get your first policy written by the Cerbos team