Authentication
Installing Required Dependencies
First, install @nestjs/passport, @nestjs/jwt, and related JWT parsing libraries:
npm install --save @nestjs/passport passport passport-jwt @nestjs/jwt jsonwebtokenCreating JWT Module and Strategy
JWT Strategy
Create a jwt.strategy.ts file to define the JWT strategy for parsing and validating JWTs.
// jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
// Here, you can retrieve user information based on the `payload` (decoded JWT) and return it
return { userId: payload.sub, email: payload.email };
}
}Constants File
Create a constants.ts file to store the JWT secret key.
// constants.ts
export const jwtConstants = {
secret: 'your_jwt_secret_key', // Replace with a secure key
};Setting Up the Passport Module
In app.module.ts, import the required modules and configure Passport and the JWT strategy.
// app.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { JwtStrategy } from './jwt.strategy';
import { AuthService } from './auth.service'; // Assume this service handles authentication logic
@Module({
imports: [
PassportModule.register({ defaultStrategy: 'jwt' }),
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '3600s' }, // Valid for 1 hour
}),
],
providers: [AuthService, JwtStrategy],
})
export class AppModule {}Implementing Login and Issuing JWT
Create an auth.service.ts file to handle user login and JWT generation.
// auth.service.ts
import { Injectable } from '@nestjs/common';
import * as jwt from 'jsonwebtoken';
import { jwtConstants } from './constants';
@Injectable()
export class AuthService {
async login(user: any) {
const payload = { sub: user.id, email: user.email };
return {
access_token: this.generateJWT(payload),
};
}
private generateJWT(payload: any) {
return jwt.sign(payload, jwtConstants.secret, { expiresIn: '3600s' });
}
}Implementing Authentication Guard
Create an auth.guard.ts file to protect your routes.
// auth.guard.ts
import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class AuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext) {
return super.canActivate(context);
}
}Applying Authentication
In your controllers, use the @UseGuards(AuthGuard) decorator to protect routes.
// cats.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('cats')
export class CatsController {
@Get()
@UseGuards(AuthGuard)
findAll(): string {
return 'This route requires authentication';
}
}Role-Based Model
First, define user roles in your data model, typically by adding a field to the user table to indicate roles such as admin, user, or moderator.
Example Code (user.entity.ts):
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@Column({ enum: ['admin', 'user', 'moderator'], default: 'user' })
role: 'admin' | 'user' | 'moderator';
}Implementing Role Checks
Create a service or guard to check if a user has the necessary permissions to access specific resources.
Role Guard (roles.guard.ts):
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true; // Allow access if no roles are specified
}
const request = context.switchToHttp().getRequest();
const user = request.user; // Assume user info from JWT is injected into the request
return roles.includes(user.role);
}
}Applying Role Guard
Use the @Roles() decorator on controller methods to specify required roles and @UseGuards(RolesGuard) to enable role checks.
Example Code (admin.controller.ts):
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RolesGuard } from './guards/roles.guard';
import { Roles } from './roles.decorator'; // Assume a custom decorator for roles
@Controller('admin')
export class AdminController {
@UseGuards(JwtAuthGuard, RolesGuard) // First verify JWT, then check roles
@Roles('admin') // Specify admin role required
@Get()
getAdminResource() {
return { message: 'Welcome to the admin dashboard!' };
}
}Configuring Reflection Metadata
To make the @Roles() decorator work, configure reflection metadata at the module level.
app.module.ts:
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { RolesGuard } from './guards/roles.guard';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
@Module({
// ...
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard, // Apply JWT guard globally
},
RolesGuard, // Register roles guard
],
})
export class AppModule {}Authorization
Defining Permissions
Permissions are typically associated with specific actions, such as “create article,” “delete article,” or “edit user profile.” You can define permissions as strings or enum values and associate them with roles.
Example Code (permissions.enum.ts):
export enum Permission {
CREATE_POST = 'create_post',
EDIT_POST = 'edit_post',
DELETE_POST = 'delete_post',
EDIT_USER_PROFILE = 'edit_user_profile',
}Updating the User Model
Add a field to the user model to store the user’s permissions.
Example Code (user.entity.ts):
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Permission } from './permissions.enum';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
password: string;
@Column({ enum: ['admin', 'user', 'moderator'], default: 'user' })
role: 'admin' | 'user' | 'moderator';
@Column({ type: 'simple-array', nullable: true })
permissions: Permission[];
}Updating Role Checks
Modify the roles guard to check if the user has the required permissions to perform specific actions.
Example Code (permissions.guard.ts):
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Permission } from './permissions.enum';
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredPermissions = this.reflector.get<Permission[]>('permissions', context.getHandler());
if (!requiredPermissions) {
return true; // Allow access if no permissions are specified
}
const request = context.switchToHttp().getRequest();
const user = request.user; // Assume user info from JWT is injected into the request
return requiredPermissions.some(permission => user.permissions?.includes(permission));
}
}Applying Permission Checks
Use the @Permissions() decorator on controller methods to specify required permissions and @UseGuards(PermissionsGuard) to enable permission checks.
Example Code (posts.controller.ts):
import { Controller, Get, UseGuards } from '@nestjs/common';
import { PermissionsGuard } from './guards/permissions.guard';
import { Permissions } from './permissions.decorator'; // Assume a custom decorator for permissions
import { Permission } from './permissions.enum';
@Controller('posts')
export class PostsController {
@UseGuards(JwtAuthGuard, PermissionsGuard) // First verify JWT, then check permissions
@Permissions(Permission.CREATE_POST) // Specify create_post permission required
@Get('create')
createPost() {
return { message: 'You are authorized to create posts.' };
}
}Updating User Service
Update the user service to allow administrators or superusers to assign permissions to other users.
Example Code (users.service.ts):
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { Permission } from './permissions.enum';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async assignPermissions(userId: number, permissions: Permission[]): Promise<User> {
const user = await this.usersRepository.findOne(userId);
if (!user) {
throw new Error('User not found');
}
user.permissions = permissions;
return this.usersRepository.save(user);
}
}Encryption and Hashing
Hashing
Hashing is a one-way process that converts data of arbitrary length into a fixed-length string. Hashing algorithms like SHA-256 or bcrypt are commonly used for password storage because they are irreversible, making it difficult to recover the original password even if the hash is exposed.
Using bcrypt for Password Hashing
bcrypt is a widely used password hashing algorithm that automatically handles salt generation and hashing.
Install bcrypt:
npm install bcryptExample Code (auth.service.ts):
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
async hashPassword(password: string): Promise<string> {
const saltRounds = 10;
return bcrypt.hash(password, saltRounds);
}
async comparePassword(password: string, hashedPassword: string): Promise<boolean> {
return bcrypt.compare(password, hashedPassword);
}
}Encryption
Encryption is a two-way process used to protect data confidentiality. Common encryption algorithms include AES and RSA. In NestJS, you can use the crypto module for encryption and decryption.
Using Node.js’s crypto Module for Encryption
Node.js’s crypto module provides encryption and decryption functionality.
Install crypto:
npm install cryptoExample Code (encryption.service.ts):
import { Injectable } from '@nestjs/common';
import * as crypto from 'crypto';
@Injectable()
export class EncryptionService {
private key = 'your-secret-key'; // The key should be stored securely
private iv = 'initial-vector'; // The initialization vector should also be stored securely
encrypt(text: string): string {
const cipher = crypto.createCipheriv('aes-256-cbc', this.key, this.iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
}
decrypt(encrypted: string): string {
const decipher = crypto.createDecipheriv('aes-256-cbc', this.key, this.iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}Usage Example
The following example demonstrates how to use the above services in a NestJS application.
Example Code (auth.controller.ts):
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { EncryptionService } from './encryption.service';
@Controller('auth')
export class AuthController {
constructor(
private authService: AuthService,
private encryptionService: EncryptionService,
) {}
@Post('register')
async register(@Body('password') password: string): Promise<void> {
const hashedPassword = await this.authService.hashPassword(password);
// Store hashedPassword in the database
}
@Post('encrypt')
async encryptData(@Body('data') data: string): Promise<string> {
const encryptedData = this.encryptionService.encrypt(data);
return encryptedData;
}
@Post('decrypt')
async decryptData(@Body('data') data: string): Promise<string> {
const decryptedData = this.encryptionService.decrypt(data);
return decryptedData;
}
}Helmet
Installing Helmet
First, install the @nestjs/helmet package, the official NestJS adapter for Helmet.
npm install @nestjs/helmetConfiguring and Using Helmet
After installation, configure and use Helmet in your NestJS application.
Import and Enable Helmet Module: In your main module (typically app.module.ts), import HelmetModule and add it to the imports array.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HelmetModule } from '@nestjs/helmet';
@Module({
imports: [HelmetModule.forRoot()], // Enable Helmet
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}By default, HelmetModule.forRoot() enables a set of recommended security headers, covering XSS protection, Content Security Policy (CSP), clickjacking protection, and more.
Custom Configuration: To customize Helmet’s configuration, pass a configuration object to the forRoot method.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { HelmetModule, HelmetOptions } from '@nestjs/helmet';
const helmetOptions: HelmetOptions = {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'", 'https://apis.google.com'],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https://'],
connectSrc: ["'self'", 'ws:', 'wss:', 'https://apis.google.com'],
},
},
};
@Module({
imports: [HelmetModule.forRoot(helmetOptions)], // Custom configuration
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}In this example, we customized the Content Security Policy (CSP) to allow loading scripts, styles, and images from specific sources.
CORS
Concept: CORS (Cross-Origin Resource Sharing) is a mechanism that uses additional HTTP headers to allow a web application running at one origin (domain) to access specified resources from a different origin server. A cross-origin HTTP request occurs when a resource requests another resource from a different domain, protocol, or port.
Purpose: The primary purpose of CORS is to ensure security while supporting cross-origin HTTP requests, preventing malicious websites from making unauthorized cross-origin data requests.
Implementation:
The server adds fields like Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers to the response headers to specify which origins can access resources, which HTTP methods are allowed, and which headers are permitted.
How to Implement CORS Protection:
- Response Header Configuration: The server adds
Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Headers, and other fields to the HTTP response headers to explicitly allow specific origins, HTTP methods, and request headers. - Preflight Requests (OPTIONS Requests): For complex requests (e.g., those using special HTTP methods or custom headers), the browser sends an OPTIONS request as a preflight to check if the server allows the actual cross-origin request.
CSRF Protection
CSRF (Cross-Site Request Forgery) is an attack where a malicious actor exploits a user’s authenticated session to perform unauthorized actions. For example, an attacker could construct a form on a third-party site that submits to the target site, using the victim’s browser cookies to authenticate the request automatically.
How to Implement CSRF Protection:
- Using CSRF Tokens: The server generates a hidden CSRF token (a random, unpredictable string) when rendering a form and stores it in the session or database. When the form is submitted, the token is included alongside the form data. The server verifies the token upon receiving the request, processing the action only if it matches.
- SameSite Cookie Attribute: For some scenarios, setting the
SameSiteattribute of cookies toLaxorStrictcan reduce CSRF risks, especially for operations that do not require cross-site requests. - Double Token Validation: Combine Bearer Tokens (e.g., JWT) for API authentication with CSRF Tokens for form submissions or other sensitive operations.
Rate Limiting
Rate Limiting
Rate limiting is a mechanism to restrict the frequency of client requests, preventing API abuse or DDoS attacks. In NestJS, you can use the express-rate-limit library to implement rate limiting.
Example Code
Install express-rate-limit:
npm install express-rate-limitCreate a rate-limiting middleware (rate-limiter.middleware.ts):
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import RateLimit from 'express-rate-limit';
@Injectable()
export class RateLimiterMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const limiter = new RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
});
limiter(req, res, next);
}
}Apply the rate-limiting middleware in the application module (app.module.ts):
import { Module, MiddlewareConsumer } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RateLimiterMiddleware } from './rate-limiter.middleware';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(RateLimiterMiddleware)
.forRoutes('*'); // Apply to all routes
}
}Multiple Rate Limiters
You can define multiple rate limiters for different routes or user groups.
Example Code (multiple-rate-limiters.middleware.ts):
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import RateLimit from 'express-rate-limit';
@Injectable()
export class MultipleRateLimitersMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const apiLimiter = new RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many API requests from this IP, please try again later.',
});
const loginLimiter = new RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 10, // Limit each IP to 10 login attempts per windowMs
message: 'Too many login attempts from this IP, please try again later.',
});
if (req.path.startsWith('/api')) {
apiLimiter(req, res, next);
} else if (req.path.startsWith('/login')) {
loginLimiter(req, res, next);
} else {
next(); // Unrestricted routes
}
}
}Customization
You can customize the rate-limiting middleware behavior, such as using different storage backends.
Example Code (custom-rate-limiter.middleware.ts):
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import RateLimit from 'express-rate-limit';
import RateLimitMongoDB from 'rate-limiter-flexible/mongodb';
@Injectable()
export class CustomRateLimiterMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const mongoConnection = /* Obtain MongoDB connection */;
const limiter = new RateLimit({
store: new RateLimitMongoDB({
uri: mongoConnection,
databaseName: 'mydb',
collectionName: 'ratelimit',
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
});
limiter.consume(req.ip).then(() => {
next();
}).catch((reason) => {
res.status(429).json(reason);
});
}
}Proxy
If using a proxy server, configure express-rate-limit to identify the correct IP address.
Example Code (proxy-rate-limiter.middleware.ts):
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import RateLimit from 'express-rate-limit';
@Injectable()
export class ProxyRateLimiterMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const limiter = new RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
ipKeyPrefix: 'ip:', // Default value
legacyHeaders: false, // Do not send rate-limiting headers
skipFailedRequests: true, // Count only successful requests
headers: true, // Send rate-limiting headers
onLimitReached: (req, res) => {
res.status(429).send('Too Many Requests');
},
trustProxies: 1, // Use the first proxy IP
});
limiter(req, res, next);
}
}WebSockets
For WebSockets, you can use the ws library with express-rate-limit to implement rate limiting.
Example Code (websocket-rate-limiter.middleware.ts):
import { Injectable } from '@nestjs/common';
import { Server, Socket } from 'socket.io';
import RateLimit from 'express-rate-limit';
@Injectable()
export class WebSocketRateLimiter {
apply(wsServer: Server) {
const limiter = new RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
});
wsServer.on('connection', (socket: Socket) => {
socket.on('message', (data: any) => {
limiter.consume(socket.handshake.address).then(() => {
socket.emit('response', data); // Process message normally
}).catch(() => {
socket.emit('error', 'Too many requests from this IP, please try again later.');
});
});
});
}
}GraphQL
For GraphQL requests, you can use GraphQL middleware to limit request frequency.
Example Code (graphql-rate-limiter.middleware.ts):
import { Injectable } from '@nestjs/common';
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
import RateLimit from 'express-rate-limit';
@Injectable()
export class GraphQLRateLimiter implements ApolloServerPlugin {
applyMiddleware({ app }: { app: any }) {
const limiter = new RateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
});
app.use('/graphql', limiter);
}
}Configuration
You can use NestJS’s configuration management library to manage rate-limiting configurations.
Example Code (config.module.ts):
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RateLimiterMiddleware } from './rate-limiter.middleware';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
],
providers: [RateLimiterMiddleware],
})
export class ConfigModule {}Asynchronous Configuration
You can use asynchronous configuration to dynamically load rate-limiting settings.
Example Code (config.module.ts):
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { RateLimiterMiddleware } from './rate-limiter.middleware';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [() => ({
rateLimit: {
windowMs: process.env.RATE_LIMIT_WINDOW_MS || 15 * 60 * 1000,
max: process.env.RATE_LIMIT_MAX || 100,
},
})],
}),
],
providers: [RateLimiterMiddleware],
})
export class ConfigModule {}Storage
In addition to in-memory storage, libraries like rate-limit-redis, rate-limit-memcached, and rate-limit-mongodb can be used to store rate-limiting data in external storage systems such as Redis, Memcached, or MongoDB, enabling shared rate limiting in distributed deployments.
Using Redis as Storage
First, install the rate-limit-redis library:
npm install rate-limit-redis ioredisThen, modify the rate-limiting middleware to use Redis storage:
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import RateLimit from 'express-rate-limit';
import RedisStore from 'rate-limit-redis';
import * as redis from 'ioredis';
@Injectable()
export class RedisRateLimiterMiddleware implements NestMiddleware {
private redisClient = redis.createClient({
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT),
});
use(req: Request, res: Response, next: NextFunction) {
const limiter = new RateLimit({
store: new RedisStore({
client: this.redisClient,
prefix: 'rateLimit:',
}),
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
});
limiter(req, res, next).catch(err => {
console.error(`Rate limiting error: ${err}`);
res.status(500).send('An error occurred while processing the request.');
});
}
}Time Helpers
In rate-limiting logic, accurately handling time windows is crucial. The ms library in Node.js simplifies time unit conversions, making time window settings more intuitive.
Install ms Library:
npm install msUse the ms library to simplify time configuration:
import * as ms from 'ms';
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import RateLimit from 'express-rate-limit';
@Injectable()
export class TimeHelperRateLimiterMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const windowMs = ms('15m'); // Convert '15m' to milliseconds
const limiter = new RateLimit({
windowMs,
max: 100,
message: 'Too many requests from this IP, please try again later.',
});
limiter(req, res, next);
}
}



