Express | TypeScript | MongoDB | Docker. Part 3
In this third part of the guide, we will implement security controls:
- Authentication & Authorization: User identity management and Role-Based Access Control (RBAC) using JSON Web Tokens (JWT).
- Route Protection: Protecting private endpoints with custom authentication middleware.
User Roles in Mongoose Schema
Update src/models/user.model.ts to include role support:
src/models/user.model.ts
import mongoose, { Schema, Document } from 'mongoose';
export enum UserRole {
ADMIN = 'ADMIN',
USER = 'USER'
}
export interface IUser extends Document {
name: string;
email: string;
password: string;
roles: UserRole[];
createdAt: Date;
updatedAt: Date;
}
const userSchema = new Schema<IUser>(
{
name: { type: String, required: true },
email: { type: String, required: true, unique: true, index: true },
password: { type: String, required: true, select: false },
roles: { type: [String], enum: Object.values(UserRole), default: [UserRole.USER] }
},
{ timestamps: true, collection: 'users' }
);
export const UserModel = mongoose.model<IUser>('User', userSchema);
JWT Dependency Installation
Install jsonwebtoken and its type definitions:
npm install jsonwebtoken
npm install --save-dev @types/jsonwebtoken
JWT Authentication Middleware
Create the JWT verification middleware:
src/middlewares/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
export interface AuthenticatedRequest extends Request {
user?: {
id: string;
roles: string[];
};
}
export const authenticateJWT = (
req: AuthenticatedRequest,
res: Response,
next: NextFunction
) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Authorization token required' });
}
const token = authHeader.split(' ')[1];
const secret = process.env.JWT_SECRET || 'default_secret';
try {
const decoded = jwt.verify(token, secret) as { id: string; roles: string[] };
req.user = decoded;
next();
} catch (err) {
return res.status(403).json({ message: 'Invalid or expired token' });
}
};
export const authorizeRoles = (...allowedRoles: string[]) => {
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ message: 'Unauthenticated' });
}
const hasRole = req.user.roles.some((role) => allowedRoles.includes(role));
if (!hasRole) {
return res.status(403).json({ message: 'Forbidden: Insufficient privileges' });
}
next();
};
};
Protecting Routes
Apply authentication and role authorization to protected routes:
src/routes/boardgame.routes.ts
import { Router } from 'express';
import { authenticateJWT, authorizeRoles } from '../middlewares/auth.middleware';
import { deleteBoardGame } from '../controllers/boardgame.controller';
const router = Router();
// Only ADMIN users can delete board games
router.delete(
'/:id',
authenticateJWT,
authorizeRoles('ADMIN'),
deleteBoardGame
);
export default router;