DEV Community

Tobias Gleiter
Tobias Gleiter

Posted on

Protect Your API-Endpoints in Next.js 14 with the Chain of Responsibility Pattern.

Introduction

Today we will do two things with the Chain of Responsibility Pattern (CoR):

  1. Implement the Chain of Responsibility Pattern in Next.js
  2. Add an authentication step to the chain to protect our API-Endpoint

This is moreover a copy and paste guide and then understand it later when using it in your code. But have fun!

Use it for Security

The CoR can be used for different security steps into your API-Endpoints. For example to check if the user is authenticated or not. The flow for that use case looks like that:

Chain of Responsibility

And that's all for the authentication. But yeah, we could do this with a simple if-else. But let’s say we want to add all steps from a security standard like the OWASP ASVS to our API-Endpoint. This would be too many if-else conditions.

Explanation

We can call all chain or steps in the CoR the same way. I name them with “Handler”. So the chain or step that checks if the user is authenticated is the “AuthenticationHandler”.

Let’s have a look at the Base Request Handler, which is the blue print for our other handlers. It is called Request because I want to handle the Request with this.

Base Handler

I am using TypeScript (TS) what makes it easier and faster to see errors.

// /lib/handler/request.handler.ts
interface Handler {
  setNext(_handler: Handler): Handler;
  handle(request: Request, context: any): Promise<Error | null>;
}

export abstract class AbstractRequestHandler implements Handler {
  private nextHandler!: Handler;

    // We need this method to set the next handler in our chain
    // AuthenticationHandler => ValidationHandler => ...
  public setNext(handler: Handler): Handler {
    this.nextHandler = handler;
    return handler;
  }

    // This method is needed to "start" the chain.
    // It is called once (you are going to see it later)
    // It handles each step in our CoR (Authenticaition => Validati..)
  public async handle(
    request: Request,
    context: Context,
  ): Promise<Error | null> {
    if (this.nextHandler) {
        // use the await keyword when you need to handle async methods
      return await this.nextHandler.handle(request, context);
    }

    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

Authentication Handler

Let’s code our first “Handler”. It’s going to be the AuthenticationHandler becuse many Application need Authentication of API-Endpoints in the first place.

**//** /lib/handler/**authentication.handler.ts

// We need to import out abstract request handler because our new handler
// should be a child of our Handler (so we could use methods from the parent)
import { AbstractRequestHandler } from "./request.handler";

// This is our first handler in the chain:
export class UserAuthenticationHandler extends AbstractRequestHandler {
    // We get two parameters: request and _context
    // Request pocesses for example the json body and headers
    // Context is in nextjs currently the search param e.g. /api/handler/[id]/route.ts
  public async handle(request: Request, _context: any): Promise<Error | null> {
    // The handler follow all the same approach:
    // Check if something true or false (in this case the session)
    // if false throw an error else go on with the next chain/step.
    const isCurrentSessionIsAvailable = true;
    if (!isCurrentSessionIsAvailable) {
        // Throw an error so we could use this in our route to "catch" the error
        // Instead of using a flag value
      throw Error("Forbidden");
    }

        // If session is "true" then we go on with the next chain/step
    return super.handle(request, _context);
  }
}**
Enter fullscreen mode Exit fullscreen mode

Implement into a Route.ts (Next.js 14)


// /api/v1/cor/route.ts

import { UserAuthenticationHandler } from "@/lib/handler/authentication.handler";
import { NextResponse } from "next/server";

export async function POST(req: Request, _context: any) {
    // In each route we have to initiate the handler we build.
  const userAuthenticationHandler = new UserAuthenticationHandler();
  //// Add Handlers as you need: (Implement them first in your /lib/handler/example.handler.ts)
  //const exampleHandler = new ExampleHandler();

  // Set the chain (there is no real chain at the moment)
  // Set the next chain/step with ".setNext(yourHandlerName)"
  userAuthenticationHandler//.setNext(exampleHandler);

  try {
      // In nextjs we have to clone the request because if we call await once
      // on the request we can't use access the body in our busines logic.
    const requestClone = req.clone();
    // Pass the request clone to the handle function which starts
    // the chain of checks we setup above the try block.
    await userAuthenticationHandler.handle(requestClone, _context);


    // After this comment the code runs only if the CoR don't throw an error!
    // YOUR BUSINESS LOGIC

    return NextResponse.json("All good!", { status: 201 });
  } catch (error) {
      // Return Forbidden to the client or handle it your way!
    return NextResponse.json("Forbidden" { status: 403 });
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion and Future topics

And that's all. You should now know how to use the CoR to protect your API-Endpoints. Just try it out. Copy the code and change some things. You can find this code on GitHub: https://github.com/TobiasGleiter/nextjs-security.

In the future we should add some things to make our application more secure. For this it is useful to use a standard like the OWASP ASVS which is a security standard that provides a checklist of security steps. For example we should verify all input using a validation Framework like Zod and validate HTTP Headers and Cookies.

References

Code: https://github.com/TobiasGleiter/nextjs-security
Me: https://tobiasgleiter.de
OWASP ASVS: https://owasp.org/www-project-application-security-verification-standard/tobvi
CoR: https://refactoring.guru/design-patterns/chain-of-responsibility

Top comments (0)