RBAC (Role-Based Access Control) is the security layer that determines which specific resources or actions a user can access after successfully logging in.

While Authentication (AuthN) identifies who a user is, Authorization (AuthZ) determines what they are permitted to do. In short, AuthN validates the identity, whereas AuthZ manages the access rights.

Many developers starting with Express.js choose Passport.js for handling authentication. A common approach is to use the JWT (JSON Web Token) strategy to secure endpoints.

Let’s create a simple «Protected» endpoint to see how “passport-jwt” works in practice.

import express, { Request, Response, NextFunction } from 'express';
import passport from 'passport';

const app = express();

interface UserPayload {
  id: string;
  username: string;
}

app.get(  
  '/dashboard',
  passport.authenticate('jwt', { session: false }),
  (req: Request, res: Response) => {
    const user = req.user as UserPayload;

    res.json({
      success: true,
      message: "Access Granted! Welcome to the protected route.",
      user: {
        id: user.id,
        username: user.username
      }
    });
  }
);

const PORT: number = 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));

While this setup grants access based on a valid JWT, it doesn’t handle RBAC out of the box. Since Passport.js focuses strictly on authentication, implementing role-based restrictions requires custom middleware to check the user’s permissions.

Let’s build our authentication logic to handle both JWT-based verification and role-based authorization where needed.

In the first part, we will create a class and define the necessary initialization methods.

import { Request, Response, NextFunction } from 'express';

interface AuthConfig {
  secretKey: string;
  roleKey?: string;
}

export class ExpressAuth {
  constructor(options: AuthConfig) {
    // Initialization logic: setting secrets and role mapping
  }

  /**
   * General Authentication Middleware
   * Use this to protect routes that only require a valid JWT, 
   * regardless of the user's role.
   */
  public AuthGuard(req: Request, res: Response, next: NextFunction): void {
    // 1. Verify token
    // 2. Attach user info to the request object
    // 3. Handle 401 Unauthenticated errors
  }

  /**
   * Role-Based Access Control (RBAC) Middleware
   * Use this to restrict access to specific roles (e.g., 'admin', 'editor').
   * It performs both authentication and role verification.
   */
  public RoleGuard(...allowedRoles: string[]) {
    return (req: Request, res: Response, next: NextFunction): void => {
      // 1. Verify token
      // 2. Extract role from payload
      // 3. Check if user's role is in allowedRoles
      // 4. Handle 403 Forbidden errors
    };
  }

  /**
   * Internal Utility
   * A private method to handle the heavy lifting of extracting the 
   * Bearer token from headers and verifying the signature.
   */
  private verifyToken(req: Request): any | null {
    // 1. Extract from 'Authorization' Header
    // 2. jwt.verify logic
  }
}

In the second part, we’ll implement the full functionality of these methods and use them to protect our endpoints.

Don’t miss Part 2, where we’ll finalize the implementation and secure our API. Follow/Subscribe to stay updated and get the full source code!