Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

14. Access Refresh Token, Middleware and Cookies in Backend

Lesson 14 of 23 in the free Backend Development notes on Siksha Sarovar, written by Rohit Jangra.

What is Middleware in Express?

Middleware is a function that executes between the incoming HTTP request and the final route handler. It has access to req, res, and next. Calling next() passes control to the next middleware or route handler. Calling next(error) passes to the error handling middleware.

Request → Middleware 1 → Middleware 2 → Route Handler → Response

Types of middleware:

  • Application-level: app.use(fn) — runs for all requests
  • Router-level: router.use(fn) — runs for routes in that router
  • Route-level: router.get('/path', fn, handler) — runs for specific route
  • Error-handling: app.use((err, req, res, next) => {}) — 4 parameters

---

cookie-parser Package

npm install cookie-parser
import cookieParser from 'cookie-parser';
app.use(cookieParser());
// Now req.cookies.tokenName is accessible in all route handlers

---

Cookie Options Explained

OptionDescriptionSecurity Impact
httpOnly: trueCookie cannot be read by JavaScript (document.cookie)Prevents XSS token theft
secure: trueCookie sent only over HTTPSPrevents transmission over insecure HTTP
sameSite: 'strict'Cookie not sent on cross-site requestsStrongest CSRF protection
sameSite: 'lax'Cookie sent on safe cross-site GET requestsBalanced CSRF protection (recommended)
sameSite: 'none'Cookie sent on all cross-site requestsRequires secure: true
maxAgeExpiry in milliseconds from nowSets when cookie expires
expiresSpecific expiry Date objectSets when cookie expires
pathURL path where cookie is sentScope the cookie to a path
domainDomain cookie applies toUse for subdomain sharing

Recommended production cookie options:

const cookieOptions = {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',  // HTTPS only in prod
  sameSite: process.env.NODE_ENV === 'production' ? 'strict' : 'lax',
  maxAge: 24 * 60 * 60 * 1000,  // 1 day in milliseconds
};

---

verifyJWT Auth Middleware — Complete Implementation

The auth middleware protects routes that require authentication. It:

  1. Reads the access token from cookies or the Authorization header
  2. Verifies and decodes the JWT
  3. Fetches the user from the database
  4. Attaches user to req.user
  5. Calls next() to proceed to the route handler
import jwt from 'jsonwebtoken';
import { User } from '../models/user.model.js';
import { ApiError } from '../utils/ApiError.js';
import { asyncHandler } from '../utils/asyncHandler.js';

export const verifyJWT = asyncHandler(async (req, res, next) => {
  // Step 1: Read token from cookie OR Authorization header
  const token =
    req.cookies?.accessToken ||
    req.header('Authorization')?.replace('Bearer ', '');

  // Step 2: Token not found
  if (!token) {
    throw new ApiError(401, 'Unauthorized: No access token provided');
  }

  // Step 3: Verify token (throws JsonWebTokenError or TokenExpiredError)
  const decoded = jwt.verify(token, process.env.JWT_SECRET);

  // Step 4: Fetch user from DB (exclude sensitive fields)
  const user = await User.findById(decoded._id).select('-password -refreshToken');
  if (!user) {
    throw new ApiError(401, 'Unauthorized: User no longer exists');
  }

  // Step 5: Attach user to request object
  req.user = user;

  // Step 6: Proceed to next middleware or route handler
  next();
});

---

Using verifyJWT on Protected Routes

import { verifyJWT } from '../middlewares/auth.middleware.js';

// Public routes (no auth required)
router.post('/login', loginUser);
router.post('/register', upload.fields([...]), registerUser);

// Protected routes (auth required)
router.route('/logout').post(verifyJWT, logoutUser);
router.route('/profile').get(verifyJWT, getCurrentUser);
router.route('/change-password').post(verifyJWT, changePassword);
router.route('/update-account').patch(verifyJWT, updateAccountDetails);

---

Token Expiry Handling

When the access token expires (after 15 minutes), the jwt.verify() call throws a TokenExpiredError. The error handler middleware catches this and returns a 401 response. The frontend should then:

  1. Detect the 401 response
  2. Call the /api/auth/refresh-token endpoint with the refresh token (sent automatically via cookie)
  3. Receive a new access token
  4. Retry the original failed request

This is handled in the Axios response interceptor (see Lesson 3).

---

Custom Properties on req Object

TypeScript users need to declare custom properties added to req:

// src/types/express.d.ts
import { IUser } from '../models/user.model';

declare global {
  namespace Express {
    interface Request {
      user?: IUser;
    }
  }
}

---

Middleware Chain Example

// Multiple middleware on a single route:
router.post(
  '/videos',
  verifyJWT,                          // 1. Check authentication
  checkSubscriptionTier,              // 2. Check if user can upload (business logic)
  upload.fields([                     // 3. Handle file uploads
    { name: 'video', maxCount: 1 },
    { name: 'thumbnail', maxCount: 1 }
  ]),
  publishVideo                        // 4. Final controller
);

Each middleware calls next() on success. If any middleware calls next(error) or throws (with asyncHandler), the chain is stopped and the error middleware runs.