Home Blog Page 110

WebSockets in NestJS with @nestjs/websockets

0
nestjs fullstack course
nestjs fullstack course

In the modern web, real-time communication is increasingly becoming a necessity. Whether it’s a live chat app, real-time analytics dashboard, multiplayer game, or stock ticker, WebSockets enable persistent two-way communication between client and server.

NestJS provides first-class support for WebSockets via the @nestjs/websockets module. In this article, you’ll learn how to build real-time applications using WebSockets in NestJS, with practical examples and explanations.


Table of Contents

  1. What Are WebSockets?
  2. Why Use WebSockets in NestJS?
  3. Setting Up @nestjs/websockets
  4. Creating a WebSocket Gateway
  5. Handling WebSocket Events
  6. Broadcasting Messages
  7. Client Example with Socket.IO
  8. WebSocket Lifecycle Hooks
  9. Security Considerations
  10. Conclusion

What Are WebSockets?

WebSockets provide a full-duplex communication channel over a single TCP connection. Unlike HTTP, which is request-response based, WebSockets allow real-time interaction with low latency.

Use Cases:

  • Real-time chat
  • Live notifications
  • Gaming servers
  • Collaborative tools (e.g., Google Docs-style editing)

Why Use WebSockets in NestJS?

NestJS makes it incredibly easy to implement WebSockets using:

  • Declarative decorators
  • Modular gateway architecture
  • Built-in support for socket.io and ws engines
  • Integration with DI and NestJS lifecycle

Setting Up @nestjs/websockets

Install the necessary dependencies:

npm install --save @nestjs/websockets @nestjs/platform-socket.io socket.io

If you prefer the ws engine over socket.io, you can replace socket.io with ws.


Creating a WebSocket Gateway

A Gateway in NestJS acts as a WebSocket server. Here’s how to create one.

Step 1: Create a Gateway

// chat.gateway.ts
import {
SubscribeMessage,
WebSocketGateway,
WebSocketServer,
MessageBody,
ConnectedSocket,
OnGatewayConnection,
OnGatewayDisconnect,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway({ cors: true }) // or pass custom namespace/port
export class ChatGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server;

handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}

handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}

@SubscribeMessage('message')
handleMessage(
@MessageBody() data: string,
@ConnectedSocket() client: Socket,
) {
console.log(`Message from ${client.id}: ${data}`);
this.server.emit('message', data); // broadcast to all clients
}
}

Step 2: Register in a Module

import { Module } from '@nestjs/common';
import { ChatGateway } from './chat.gateway';

@Module({
providers: [ChatGateway],
})
export class ChatModule {}

Handling WebSocket Events

NestJS maps WebSocket events using the @SubscribeMessage() decorator. These are event-driven, similar to Express route handlers, but asynchronous and long-lived.

@SubscribeMessage('event_name')
handleCustomEvent(@MessageBody() data: any) {
// handle event here
}

Broadcasting Messages

You can emit events to all clients or selectively:

// To all clients
this.server.emit('event', payload);

// To one client
client.emit('event', payload);

// To all except sender
client.broadcast.emit('event', payload);

Client Example with Socket.IO

Here’s a minimal client using the Socket.IO client library.

<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
<script>
const socket = io('http://localhost:3000');

socket.on('connect', () => {
console.log('Connected to WebSocket server');
});

socket.emit('message', 'Hello from client!');

socket.on('message', (msg) => {
console.log('Received:', msg);
});
</script>

WebSocket Lifecycle Hooks

NestJS provides interfaces for connection lifecycle:

HookInterface
On client connectOnGatewayConnection
On client disconnectOnGatewayDisconnect
On init (optional)OnGatewayInit

Example:

handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}

handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}

Security Considerations

  • Authentication: Use middleware to verify JWT on connection.
  • Namespaces: Isolate gateways for different roles/features.
  • Rate Limiting: Prevent flooding and abuse.
  • CORS: Explicitly set CORS policies for WebSocket access.

Example: authenticating on connect

@WebSocketGateway()
export class SecureGateway implements OnGatewayConnection {
handleConnection(client: Socket) {
const token = client.handshake.headers.authorization?.replace('Bearer ', '');
if (!verifyToken(token)) {
client.disconnect();
}
}
}

Conclusion

With the @nestjs/websockets module, NestJS offers a structured and powerful abstraction over WebSocket programming. You can build scalable, real-time applications that integrate seamlessly with the rest of your NestJS ecosystem.

Middleware vs Guard for Auth Handling in NestJS

0
nestjs fullstack course
nestjs fullstack course

When building secure applications with NestJS, authentication plays a central role. Two common mechanisms used for request-level processing are Middleware and Guards. Both can be used for handling authentication, but they serve distinct purposes and operate at different stages of the request lifecycle.

In this module, we’ll explore the key differences between middleware and guards, discuss when and how to use each for authentication, and provide real-world examples for both approaches.


Table of Contents

  1. Understanding the Request Lifecycle in NestJS
  2. What is Middleware in NestJS?
  3. What are Guards in NestJS?
  4. Key Differences Between Middleware and Guards
  5. Using Middleware for Auth
  6. Using Guards for Auth
  7. When to Use Middleware vs Guard
  8. Best Practices
  9. Conclusion

Understanding the Request Lifecycle in NestJS

To decide between middleware and guards, you need to understand where they fit into the NestJS request pipeline:

Middleware → Guards → Interceptors → Controller → Service → Response
  • Middleware runs before route matching
  • Guards run after route matching but before controllers
  • Interceptors run around route handlers (for transformation, logging, etc.)

What is Middleware in NestJS?

Middleware functions are functions that run before your routes are handled. They are typically used for:

  • Logging
  • Request transformation
  • Attaching data to req object
  • Pre-processing headers

They are not route-aware by default and cannot access route metadata (like decorators).


What are Guards in NestJS?

Guards are classes implementing CanActivate and are used for authorization and authentication logic. They are fully aware of the route context and can use route-level metadata (e.g., roles, permissions).

Guards are great for:

  • Route-level access control
  • Protecting routes based on user roles
  • Preventing requests before reaching controllers

Key Differences Between Middleware and Guards

FeatureMiddlewareGuard
Execution TimeBefore route handlingAfter route match, before controller
Access to Route Metadata❌ No✅ Yes
Use CasesLogging, headers, basic parsingAuth, RBAC, request authorization
Return Value BehaviorNo return value control flowMust return true or throw error
Dependency Injection (DI)❌ Limited (via @Injectable workaround)✅ Full DI support

Using Middleware for Auth

Let’s say you want to attach a decoded JWT token to every request.

Step 1: Create the middleware

// auth.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as jwt from 'jsonwebtoken';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization']?.replace('Bearer ', '');

if (token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req['user'] = decoded;
} catch (err) {
// do not throw here — middleware shouldn’t block the request
}
}

next();
}
}

Step 2: Apply in AppModule or specific module

// app.module.ts
import { MiddlewareConsumer, Module } from '@nestjs/common';

@Module({ /* imports and providers */ })
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
}
Middleware cannot prevent route execution, which is a critical limitation for secure auth.

Using Guards for Auth

For secure route protection, use Guards with dependency injection.

Step 1: Create a guard

// jwt-auth.guard.ts
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import * as jwt from 'jsonwebtoken';

@Injectable()
export class JwtAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers['authorization']?.replace('Bearer ', '');

if (!token) {
throw new UnauthorizedException('Token missing');
}

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
request.user = decoded;
return true;
} catch (err) {
throw new UnauthorizedException('Token invalid');
}
}
}

Step 2: Apply the guard to routes

@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Req() req) {
return req.user;
}

When to Use Middleware vs Guard

SituationUse MiddlewareUse Guard
You need to parse token and attach to req
You need to protect a route
You need to read route decorators (roles)
You want to log all incoming requests
You want to apply global auth filter✅ (light) + ✅ Guard

Best Practices

  • Use middleware for lightweight pre-processing (like decoding JWTs, logging).
  • Use guards for anything related to authorization, roles, or blocking requests.
  • Use combination: middleware can decode the token, guard can verify access based on route metadata.
  • Avoid throwing exceptions inside middleware — handle errors gracefully.

Conclusion

Both middleware and guards are powerful tools in NestJS, but they solve different problems. Middleware is ideal for preprocessing, while guards provide route-aware access control. For robust authentication systems, especially where roles and permissions are involved, guards are the go-to mechanism.

Using Sessions and Cookies in NestJS: Persistent Login and Secure State Management

0
nestjs fullstack course
nestjs fullstack course

While JWTs are a popular method for authentication in modern APIs, sessions and cookies remain a secure and viable solution — especially in traditional web apps or SSR-based systems. NestJS offers seamless integration with session-based authentication, allowing you to manage user sessions and persist state across HTTP requests.

In this module, you’ll learn how to set up session-based authentication, use cookies securely, and understand the differences between JWTs and sessions in a NestJS context.


Table of Contents

  1. Introduction to Sessions and Cookies
  2. When to Use Sessions vs JWT
  3. Installing Required Packages
  4. Setting Up Sessions with express-session
  5. Using @nestjs/passport with Sessions
  6. Accessing Session Data in Controllers
  7. Using Cookies in NestJS
  8. Security Considerations: CSRF, HTTPS, HttpOnly
  9. Conclusion

Introduction to Sessions and Cookies

A session stores user data on the server and associates it with a unique identifier (usually stored in a cookie on the client). This approach allows persistent login and secure server-side state management.

Cookies are small pieces of data sent by the server and stored on the client. They are included in every HTTP request to the same origin, making them ideal for session tracking.


When to Use Sessions vs JWT

FeatureJWTSessions & Cookies
Stateless✅ Yes❌ No
Secure via HttpOnly⚠️ Manually configured✅ By default
Suitable for SPAs✅ Yes⚠️ Less suitable without SSR
Easy token rotation❌ Complex✅ Simple
Backend control❌ No (token is client-stored)✅ Yes (session stored server-side)

Installing Required Packages

Install the necessary packages for session management:

npm install express-session
npm install @nestjs/passport passport
npm install @types/express-session --save-dev

Setting Up Sessions with express-session

In your main.ts or app.module.ts, configure the middleware:

// main.ts
import * as session from 'express-session';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

app.use(
session({
secret: process.env.SESSION_SECRET || 'keyboard_cat',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
maxAge: 1000 * 60 * 60, // 1 hour
},
}),
);

await app.listen(3000);
}
bootstrap();
The secret should be stored in environment variables in production.

Using @nestjs/passport with Sessions

Enable Passport to serialize and deserialize users:

// auth.module.ts
import { PassportModule } from '@nestjs/passport';

@Module({
imports: [PassportModule.register({ session: true })],
...
})
export class AuthModule {}

In your local strategy, enable session support:

// local.strategy.ts
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super(); // username/password fields by default
}

async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user; // Automatically attached to req.user
}
}

Implement the serializeUser and deserializeUser methods in your auth service or a separate passport file:

// main.ts or auth.service.ts
import * as passport from 'passport';

passport.serializeUser((user, done) => {
done(null, user.id);
});

passport.deserializeUser((id, done) => {
// Fetch from DB
const user = { id, name: 'John Doe' }; // Replace with real DB call
done(null, user);
});

Accessing Session Data in Controllers

@Get('profile')
@UseGuards(AuthGuard('local'))
getProfile(@Req() req: Request) {
return req.user; // Available from session
}

To manually store or access session data:

@Post('login')
login(@Req() req) {
req.session.myCustomValue = 'someData';
return { message: 'Logged in!' };
}

Using Cookies in NestJS

You can also work directly with cookies:

Reading Cookies:

@Get('cookies')
getCookies(@Req() req) {
return req.cookies;
}

Setting Cookies:

@Post('set-cookie')
setCookie(@Res() res: Response) {
res.cookie('theme', 'dark', {
httpOnly: true,
secure: true,
maxAge: 60000,
});
return res.send('Cookie set');
}

Make sure to enable cookie parser:

npm install cookie-parser
// main.ts
import * as cookieParser from 'cookie-parser';
app.use(cookieParser());

Security Considerations: CSRF, HTTPS, HttpOnly

  • Always use HTTPS in production when working with cookies.
  • Enable the HttpOnly and Secure flags on cookies to prevent XSS and ensure transport security.
  • Consider implementing CSRF protection if your app uses cookies for session authentication.

Conclusion

Sessions and cookies in NestJS provide a robust and secure way to manage user authentication and server-side state. They’re particularly well-suited for SSR applications, admin panels, and traditional web services. While stateless JWT authentication is ideal for APIs, sessions shine in cases where server control and fine-grained security are essential.

Role-Based Access Control (RBAC) and Guards in NestJS

0
nestjs fullstack course
nestjs fullstack course

Controlling access to resources based on user roles is a fundamental security practice in modern application development. In NestJS, Guards offer a clean and extensible way to implement Role-Based Access Control (RBAC) at the route or controller level.

This module walks you through how to implement RBAC using custom decorators and guards in NestJS to protect routes based on user roles.


Table of Contents

  1. What is RBAC?
  2. How NestJS Guards Help
  3. Defining User Roles
  4. Creating a Roles Decorator
  5. Implementing a Roles Guard
  6. Using Roles Guard in Controllers
  7. Combining with JWT Auth Guard
  8. Best Practices
  9. Conclusion

What is RBAC?

Role-Based Access Control (RBAC) is a system where each user is assigned a role, and each role has specific permissions. For example:

  • Admin: full access
  • Editor: can edit but not delete
  • User: read-only access

RBAC ensures that only authorized users can access certain resources or perform specific actions.


How NestJS Guards Help

Guards in NestJS determine whether a request will be handled by the route handler. They are ideal for enforcing authentication and authorization rules.

Guards return true to proceed or throw an exception (like ForbiddenException) to deny access.


Defining User Roles

First, define a role enum:

// roles.enum.ts
export enum Role {
User = 'user',
Editor = 'editor',
Admin = 'admin',
}

Add a roles field in your User entity or DTO:

// user.entity.ts
export class User {
id: number;
email: string;
password: string;
roles: Role[];
}

Creating a Roles Decorator

Custom decorators allow us to attach metadata to route handlers, which guards can later read.

// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import { Role } from './roles.enum';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);

Implementing a Roles Guard

The guard checks the user’s roles against the required roles defined via the decorator.

// roles.guard.ts
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';
import { Role } from './roles.enum';

@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}

canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);

if (!requiredRoles || requiredRoles.length === 0) {
return true; // No role restriction
}

const request = context.switchToHttp().getRequest();
const user = request.user;

if (!user?.roles) {
throw new ForbiddenException('No roles found for user');
}

const hasRole = user.roles.some((role: Role) =>
requiredRoles.includes(role),
);

if (!hasRole) {
throw new ForbiddenException('Access denied');
}

return true;
}
}

Using Roles Guard in Controllers

// posts.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { Roles } from '../auth/roles.decorator';
import { Role } from '../auth/roles.enum';
import { RolesGuard } from '../auth/roles.guard';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';

@Controller('posts')
@UseGuards(JwtAuthGuard, RolesGuard)
export class PostsController {
@Get('admin')
@Roles(Role.Admin)
findAllForAdmin() {
return 'Only admins can access this route';
}

@Get('editor')
@Roles(Role.Editor, Role.Admin)
findAllForEditor() {
return 'Admins and Editors can access this route';
}
}

Combining with JWT Auth Guard

In a real-world app, the roles are often decoded from the JWT payload. Modify your JWT strategy to include roles in the token:

async validate(payload: any) {
return { userId: payload.sub, email: payload.email, roles: payload.roles };
}

Ensure roles are included during login:

async login(user: any) {
const payload = {
sub: user.id,
email: user.email,
roles: user.roles,
};

return {
accessToken: this.jwtService.sign(payload),
};
}

Best Practices

  • Define roles centrally and document them.
  • Avoid hardcoding roles in multiple places.
  • Always validate that roles exist before issuing tokens.
  • Consider combining RBAC with attribute-based access control (ABAC) for more flexibility in the future.

Conclusion

RBAC using NestJS guards provides a powerful and declarative way to protect routes and resources based on user roles. By combining custom decorators, guards, and JWT-based authentication, you can build scalable and secure APIs.

Refresh Tokens and Token Rotation in NestJS: Secure JWT Authentication

0
nestjs fullstack course
nestjs fullstack course

As you build secure applications with NestJS and JWT authentication, one critical concern is token expiration. Access tokens should have short lifespans for security, but that often forces users to log in frequently. This is where refresh tokens come in — allowing you to issue new access tokens without requiring the user to log in again.

In this module, you’ll learn how to implement refresh tokens, understand token rotation, and ensure your authentication flow is both user-friendly and secure.


Table of Contents

  1. What Are Refresh Tokens?
  2. Why Use Refresh Tokens?
  3. How Refresh Token Flow Works
  4. Generating and Issuing Refresh Tokens
  5. Storing Refresh Tokens Securely
  6. Token Rotation: Preventing Replay Attacks
  7. Implementing Refresh Token Endpoint in NestJS
  8. Best Practices
  9. Conclusion

What Are Refresh Tokens?

A refresh token is a long-lived token that is used to obtain a new access token after the original one expires. Unlike the access token, which is usually valid for a few minutes, refresh tokens can last for days or even weeks.


Why Use Refresh Tokens?

  • Short-lived access tokens reduce the risk of token theft.
  • User doesn’t have to log in again after the token expires.
  • Enables automatic session renewal (e.g., mobile apps, SPAs).
  • Supports secure token rotation mechanisms.

How Refresh Token Flow Works

  1. Login: User provides credentials → server returns an access token + refresh token.
  2. Access Token Expires: Client detects expiry.
  3. Refresh Request: Client sends the refresh token to a dedicated endpoint.
  4. New Token Issued: Server verifies refresh token → generates a new access token (and optionally a new refresh token).
  5. Token Rotation (optional): Replace the used refresh token with a new one.

Generating and Issuing Refresh Tokens

You can generate a refresh token similarly to an access token, but with a different secret and longer expiration:

// auth.service.ts
async generateTokens(user: any) {
const payload = { sub: user.id, email: user.email };

const accessToken = this.jwtService.sign(payload, {
secret: process.env.ACCESS_TOKEN_SECRET,
expiresIn: '15m',
});

const refreshToken = this.jwtService.sign(payload, {
secret: process.env.REFRESH_TOKEN_SECRET,
expiresIn: '7d',
});

return { accessToken, refreshToken };
}

Storing Refresh Tokens Securely

Options:

  • HTTP-only cookies (recommended for web apps).
  • Database (to support token revocation and rotation).
  • Client-side local storage (less secure, not recommended).

Example: Storing hashed refresh tokens in DB

async storeRefreshToken(userId: number, token: string) {
const hashed = await bcrypt.hash(token, 10);
await this.usersService.update(userId, { refreshToken: hashed });
}

Token Rotation: Preventing Replay Attacks

Token rotation means replacing the refresh token every time it is used. This prevents replay attacks where a stolen token could be reused indefinitely.

Steps for rotation:

  1. Store hashed refresh tokens in the database.
  2. When a refresh token is used:
    • Validate it.
    • Invalidate/delete it.
    • Issue and store a new one.
  3. Reject reused or expired tokens.

Implementing Refresh Token Endpoint in NestJS

Here’s an example of a /auth/refresh endpoint:

// auth.controller.ts
@Post('refresh')
async refresh(@Body() body: { refreshToken: string }) {
const user = await this.authService.verifyRefreshToken(body.refreshToken);
const tokens = await this.authService.generateTokens(user);
await this.authService.storeRefreshToken(user.id, tokens.refreshToken);
return tokens;
}
// auth.service.ts
async verifyRefreshToken(token: string) {
try {
const payload = this.jwtService.verify(token, {
secret: process.env.REFRESH_TOKEN_SECRET,
});

const user = await this.usersService.findById(payload.sub);
if (!user || !(await bcrypt.compare(token, user.refreshToken))) {
throw new UnauthorizedException('Invalid refresh token');
}

return user;
} catch {
throw new UnauthorizedException('Invalid refresh token');
}
}

Best Practices

  • Use different secrets for access and refresh tokens.
  • Use HTTP-only cookies for web clients to reduce XSS risks.
  • Store refresh tokens hashed in the database.
  • Implement refresh token rotation to prevent reuse.
  • Blacklist or expire old tokens if a new one is issued.

Conclusion

Refresh tokens significantly enhance the security and usability of your authentication system in NestJS. With proper token rotation and validation, you can prevent common vulnerabilities while ensuring seamless user sessions.