Auth & Security/
Lesson

Most apps have two kinds of routes: public ones anyone can hit, and protected ones that require authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token.. MiddlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. is how you enforce that boundary cleanly, without repeating the same tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use.-check logic in every route handlerWhat is route handler?A Next.js file named route.js inside the app/ directory that handles HTTP requests directly - the App Router equivalent of API routes..

Think of middleware like a bouncer at the door. Every request walks up, the bouncer checks their ID, and only the valid ones get waved through. The bouncer doesn't care what the person is going to do inside, that's the route handler's job.

How the request pipelineWhat is pipeline?A sequence of automated steps (install, lint, test, build, deploy) that code passes through before reaching production. works

Express processes a request through a chain of functions before it reaches your route handlerWhat is route handler?A Next.js file named route.js inside the app/ directory that handles HTTP requests directly - the App Router equivalent of API routes.. Each function in the chain receives req, res, and next. Calling next() passes control to the next function; not calling it stops the chain.

Request → requireAuth → requireRole → validate → Route Handler → Response
               |               |           |
           Check JWT       Check role   Validate body
           401/403          403          400

The order matters. You want authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. checked before authorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages., and authorization before business logic.

02

Creating auth middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it.

JWTWhat is jwt?JSON Web Token - a self-contained, signed token that carries user data (like user ID and role). The server can verify it without a database lookup. authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. middleware

// middleware/auth.js
import jwt from 'jsonwebtoken';

export function requireAuth(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

  if (!token) {
    return res.status(401).json({
      error: 'Access denied',
      message: 'No token provided'
    });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded; // Attach user for downstream use
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({
        error: 'Token expired',
        message: 'Please log in again'
      });
    }
    return res.status(403).json({
      error: 'Invalid token',
      message: 'Token verification failed'
    });
  }
}

SessionWhat is session?A server-side record that tracks a logged-in user. The browser holds only a session ID in a cookie, and the server looks up the full data on each request.-based auth middleware

If you use sessions instead of JWTs, the check is simpler, you just verify the session store has a valid entry:

export function requireSession(req, res, next) {
  if (!req.session.userId) {
    return res.status(401).json({
      error: 'Not authenticated',
      message: 'Please log in'
    });
  }

  req.user = {
    id: req.session.userId,
    role: req.session.role
  };

  next();
}
The key difference between 401 and 403: use 401 when the user isn't authenticated at all (no token, expired token). Use 403 when they are authenticated but don't have permission for the specific resource.
03

Role-based middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it.

Checking roles

Once you know who someone is, you can check what they're allowed to do. A role middleware factory takes the permitted roles as arguments and returns a middleware function:

export function requireRole(...allowedRoles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }

    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({
        error: 'Forbidden',
        message: `Role '${req.user.role}' not authorized`
      });
    }

    next();
  };
}

// Usage
app.delete('/users/:id',
  requireAuth,
  requireRole('admin', 'moderator'),
  deleteUser
);

Fine-grained permission checks

Roles are coarse-grained. For more control, you can check specific permissions stored in the database:

export function requirePermission(permission) {
  return async (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }

    const userPermissions = await getUserPermissions(req.user.id);

    if (!userPermissions.includes(permission)) {
      return res.status(403).json({
        error: 'Forbidden',
        message: `Missing permission: ${permission}`
      });
    }

    next();
  };
}

// Usage
app.post('/posts',
  requireAuth,
  requirePermission('posts:create'),
  createPost
);
04

Resource ownership

Role checks protect categories of users, but sometimes you need to protect individual resources. An invoice should only be visible to the user who created it (and admins):

export function requireOwnership(getResourceOwner) {
  return async (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }

    const resourceId = req.params.id;
    const ownerId = await getResourceOwner(resourceId);

    if (req.user.id !== ownerId && req.user.role !== 'admin') {
      return res.status(403).json({
        error: 'Forbidden',
        message: 'You do not own this resource'
      });
    }

    next();
  };
}

// Usage
app.put('/posts/:id',
  requireAuth,
  requireOwnership(async (postId) => {
    const post = await db.getPost(postId);
    return post.authorId;
  }),
  updatePost
);
Always check ownership on the server, never trust a userId sent from the client. An attacker can trivially change that value.
05

Composing middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. chains

The real power comes from chaining these pieces together. A typical set of routes looks like this:

// Public routes (no middleware)
app.post('/login', login);
app.post('/register', register);

// Protected routes
app.get('/profile', requireAuth, getProfile);
app.put('/profile', requireAuth, validate(updateProfileSchema), updateProfile);

// Admin-only routes
app.get('/admin/users', requireAuth, requireRole('admin'), getAllUsers);
app.delete('/admin/users/:id', requireAuth, requireRole('admin'), deleteUser);

// Ownership-protected routes
app.get('/posts/:id', getPost);  // Public read
app.post('/posts', requireAuth, validate(createPostSchema), createPost);
app.put('/posts/:id', requireAuth, requireOwnership(getPostOwner), updatePost);
app.delete('/posts/:id', requireAuth, requireOwnership(getPostOwner), deletePost);

The order in each chain is deliberate: check identity first, then check permissions, then validate the request body.

06

Quick reference

Status codeMeaningWhen to use
401UnauthorizedNo token, expired token, invalid credentials
403ForbiddenValid token but insufficient role or ownership
400Bad RequestValidation failed (wrong body shape)
404Not FoundResource doesn't exist (or hide existence with 403)
// Minimal auth middleware
function requireAuth(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) return res.status(401).json({ error: 'No token' });

  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    res.status(403).json({ error: 'Invalid token' });
  }
}

// Minimal role check
function requireRole(...roles) {
  return (req, res, next) => {
    if (!roles.includes(req.user?.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
}

// Wire them up
app.get('/admin', requireAuth, requireRole('admin'), handler);