Verify Authentication Middleware
The Verify Authentication Middleware protects private routes by validating user authentication using JWT access tokens and refresh tokens.
It automatically:
- Verifies access tokens from cookies
- Refreshes expired access tokens using refresh tokens
- Re-attaches authenticated user data to the request
- Responds with standardized API errors when authentication fails
This middleware is designed for secure, session-like authentication in REST APIs.
How Authentication Works
The middleware follows this sequence:
- Read
accessTokenandrefreshTokenfrom cookies - If access token is valid → allow request
- If access token is expired:
- Verify refresh token
- Validate user existence
- Issue new access & refresh tokens
- Set new cookies automatically
- If all checks fail → reject the request
This ensures seamless token rotation without forcing the user to re-login.
Installation Guide
This component requires additional ServerCN components.
👉 Note: You do not need to install any servercn dependencies manually. Installing this component will automatically install all required servercn dependencies. Manual installation is optional if you prefer to manage dependencies yourself.
1. Install ServerCN dependencies(Optional)
- HTTP Status Codes:
npx servercn add http-status-codesDocumentation: HTTP Status Codes
- Api Response Handler:
npx servercn add response-formatterDocumentation: Api Response Handler
- Api Error Handler:
npx servercn add error-handlerDocumentation: Api Error Handler
- JWT Authentication:
npx servercn add jwt-utilsDocumentation: JWT Authentication
- Logger:
npx servercn add loggerDocumentation: Logger
2. Install this component
npx servercn add verify-auth-middleware⚠️ If this dependency is not installed, the component will not function correctly.
Prerequisites
Ensure the following environment variables are defined in .env:
PORT = "3000";
NODE_ENV = "development";
LOG_LEVEL = "info";
JWT_ACCESS_SECRET = "your-access-secret";
JWT_REFRESH_SECRET = "your-refresh-secret";Ensure the following configuration are defined:
src/configs/env.ts
interface Config {
PORT: number;
NODE_ENV: string;
LOG_LEVEL: string;
JWT_REFRESH_SECRET: string;
JWT_ACCESS_SECRET: string;
}
const env: Config = {
PORT: Number(process.env.PORT) || 3000,
NODE_ENV: process.env.NODE_ENV || "development",
LOG_LEVEL: process.env.LOG_LEVEL || "info",
JWT_REFRESH_SECRET: process.env.JWT_ACCESS_SECRET!,
JWT_ACCESS_SECRET: process.env.JWT_ACCESS_SECRET!
};
export default env;To ensure the authentication middleware functions correctly, your project must define a User model with a structure similar to the following.
src/models/user.model.ts
import mongoose, { Document, Model, Schema } from "mongoose";
export interface IUser extends Document {
_id: mongoose.Types.ObjectId;
name: string;
email: string;
password: string;
role: "user" | "admin";
isEmailVerified: boolean;
createdAt: Date;
updatedAt: Date;
}
const userSchema = new Schema<IUser>(
{
name: {
type: String,
required: [true, "Name is required"],
trim: true
},
email: {
type: String,
required: [true, "Email is required"],
unique: true,
lowercase: true,
trim: true
},
password: {
type: String,
select: false,
default: null
},
role: {
type: String,
enum: ["user", "admin"],
default: "user"
},
isEmailVerified: {
type: Boolean,
default: false
}
},
{
timestamps: true
}
);
const User: Model<IUser> = mongoose.model<IUser>("User", userSchema);
export default User;To access authenticated user data inside request handlers, define a custom request type.
src/types/user.ts
import { Request } from "express";
import mongoose from "mongoose";
export interface UserRequest extends Request {
user?: {
_id?: string | mongoose.Types.ObjectId;
};
}Basic Implementation
1. MVC Structure
src/middlewares/verify-auth.ts
import { NextFunction, Request, Response } from "express";
import {
generateAccessToken,
generateRefreshToken,
verifyAccessToken,
verifyRefreshToken
} from "../utils/jwt";
import { UserRequest } from "../types/user";
import { ApiError } from "../utils/api-error";
import { logger } from "../utils/logger";
import User from "../models/user.model";
import env from "../configs/env";
const isProduction = env.NODE_ENV === "production";
const ACCESS_TOKEN_EXPIRY = 15 * 60 * 1000;
const REFRESH_TOKEN_EXPIRY = 7 * 24 * 60 * 60 * 1000;
export const COOKIE_OPTIONS = {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? ("none" as const) : ("lax" as const),
path: "/"
};
export async function verifyAuthentication(
req: UserRequest,
res: Response,
next: NextFunction
): Promise<void> {
const accessToken = req.cookies?.accessToken;
const refreshToken = req.cookies?.refreshToken;
// Step 1: Try validating access token
try {
if (accessToken) {
const decoded = verifyAccessToken(accessToken);
req.user = decoded;
return next();
}
} catch (err) {
// Access token expired or invalid
logger.warn("Access token verification failed");
return next(ApiError.badRequest("Invalid or expired access token"));
}
// Step 2: Refresh token required if access token fails
if (!refreshToken) {
return next(ApiError.unauthorized("Unauthorized, Please login first."));
}
try {
const decodedRefresh = verifyRefreshToken(refreshToken);
// Step 3: Ensure user still exists
const userInDb = await User.findOne({
_id: decodedRefresh.userId
});
if (!userInDb) {
return next(ApiError.unauthorized("Unauthorized, Please login first."));
}
// Step 4: Issue new tokens
const newAccessToken = generateAccessToken({
_id: userInDb._id.toString()
});
const newRefreshToken = generateRefreshToken(decodedRefresh.userId);
// Step 5: Saved accessToken and refreshToken in cookie
res.cookie("accessToken", newAccessToken, {
...COOKIE_OPTIONS,
maxAge: ACCESS_TOKEN_EXPIRY
});
res.cookie("refreshToken", newRefreshToken, {
...COOKIE_OPTIONS,
maxAge: REFRESH_TOKEN_EXPIRY
});
// Step 6: Attach user to request
req.user = {
_id: decodedRefresh.userId
};
// you can update the refresh token in the database here if you store it in the database
return next();
} catch (err: any) {
logger.warn("Refresh token verification failed");
return next(ApiError.unauthorized("Unauthorized, Please login first."));
}
}2. Feature Structure
src/shared/middlewares/verify-auth.ts
import {
generateAccessToken,
generateRefreshToken,
verifyAccessToken,
verifyRefreshToken
} from "../utils/jwt";
import { logger } from "../utils/logger";
import env from "../configs/env";
import { UserRequest } from "../../types/user";
import { ApiError } from "../errors/api-error";
import User from "../../modules/user/user.model";
import { NextFunction, Response } from "express";
const isProduction = env.NODE_ENV === "production";
const ACCESS_TOKEN_EXPIRY = 15 * 60 * 1000;
const REFRESH_TOKEN_EXPIRY = 7 * 24 * 60 * 60 * 1000;
export const COOKIE_OPTIONS = {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? ("none" as const) : ("lax" as const),
path: "/"
};
export async function verifyAuthentication(
req: UserRequest,
res: Response,
next: NextFunction
): Promise<void> {
const accessToken = req.cookies?.accessToken;
const refreshToken = req.cookies?.refreshToken;
// Step 1: Try validating access token
try {
if (accessToken) {
const decoded = verifyAccessToken(accessToken);
req.user = decoded;
return next();
}
} catch (err) {
// Access token expired or invalid
logger.warn("Access token verification failed");
return next(ApiError.badRequest("Invalid or expired access token"));
}
// Step 2: Refresh token required if access token fails
if (!refreshToken) {
return next(ApiError.unauthorized("Unauthorized, Please login first."));
}
try {
const decodedRefresh = verifyRefreshToken(refreshToken);
// Step 3: Ensure user still exists
const userInDb = await User.findOne({
_id: decodedRefresh.userId
});
if (!userInDb) {
return next(ApiError.unauthorized("Unauthorized, Please login first."));
}
// Step 4: Issue new tokens
const newAccessToken = generateAccessToken({
_id: userInDb._id.toString()
});
const newRefreshToken = generateRefreshToken(decodedRefresh.userId);
// Step 5: Saved accessToken and refreshToken in cookie
res.cookie("accessToken", newAccessToken, {
...COOKIE_OPTIONS,
maxAge: ACCESS_TOKEN_EXPIRY
});
res.cookie("refreshToken", newRefreshToken, {
...COOKIE_OPTIONS,
maxAge: REFRESH_TOKEN_EXPIRY
});
// Step 6: Attach user to request
req.user = {
_id: decodedRefresh.userId
};
// you can update the refresh token in the database here if you store it in the database
return next();
} catch (err: any) {
logger.warn("Refresh token verification failed");
return next(ApiError.unauthorized("Unauthorized, Please login first."));
}
}Usage Example
src/routes/user.routes.ts
import { Router } from "express";
import { verifyAuthentication } from "../middlewares/verify-auth";
const router = Router();
router.get("/profile", verifyAuthentication, (req, res) => {
return ApiResponse.ok(res, "User profile", req.user);
});
export default router;Error Responses
Authentication failures return standardized responses:
{
"success": false,
"message": "Unauthorized, Please login first.",
"statusCode": 401
}