Home Blog Page 146

Token Blacklisting in Node.js

0
full stack development
full stack development

Table of Contents

  1. Introduction to Token Blacklisting
  2. Why Token Blacklisting is Important
  3. Common Use Cases for Token Blacklisting
  4. How Token Blacklisting Works
  5. Implementing Token Blacklisting in Node.js
    • Using a Database
    • Using Redis for Blacklist Storage
  6. Best Practices in Token Blacklisting
  7. Conclusion

1. Introduction to Token Blacklisting

Token blacklisting refers to the process of invalidating specific authentication tokens, such as JWTs (JSON Web Tokens), before their expiry time. It ensures that tokens that have been compromised, revoked, or are otherwise no longer valid can be immediately rejected by the system.

In many applications, tokens are used for user authentication and authorization, particularly with stateless systems like REST APIs. However, there are scenarios where you might need to revoke a user’s access immediately, for example, when they log out, change their password, or their account is compromised.

While JWTs are designed to be stateless (without server-side session storage), token blacklisting introduces server-side tracking of tokens that should no longer be valid, adding a layer of security for critical use cases.


2. Why Token Blacklisting is Important

Blacklisting tokens is important because:

  • Revoking Access: If a user logs out, their token should no longer be valid.
  • Compromised Accounts: If a user’s account is compromised, you need to invalidate all active tokens associated with that user.
  • Sensitive Actions: After sensitive actions (like changing the password or email address), you may want to invalidate all tokens for security purposes.

Without token blacklisting, once a token is issued, it is valid until it expires, leaving a window of vulnerability for malicious actors to misuse the token.


3. Common Use Cases for Token Blacklisting

Some common use cases for token blacklisting include:

  • User Logout: When a user logs out, invalidate their current token so it can’t be reused.
  • Password Change: After a password change, all tokens should be invalidated, as the previous tokens were generated under the old password.
  • Account Compromise: If an account is compromised, all associated tokens need to be revoked immediately.
  • Administrative Action: When an admin performs certain actions like disabling an account, associated tokens should be blacklisted.

4. How Token Blacklisting Works

Token blacklisting typically works by storing the token’s unique identifier (often the token’s ID or the entire token itself) in a centralized store, such as a database or Redis. When a request is made to a protected route, the token is checked against the blacklist. If the token exists in the blacklist, it is rejected, and the user is denied access.

1. JWT Token Blacklisting Strategy

Since JWTs are stateless, they don’t require a central server-side session, which makes blacklisting tricky. The common approach is:

  • Generate a unique token identifier when the JWT is created.
  • Store this identifier in a blacklist whenever the token needs to be revoked (e.g., on logout).
  • When a request is made with a token, check the token’s identifier against the blacklist before allowing access.

5. Implementing Token Blacklisting in Node.js

Let’s explore how to implement token blacklisting in a Node.js application using a Database and Redis for fast lookups.

Using a Database (MongoDB Example)

  1. Install Required Packages
npm install express jsonwebtoken mongoose
  1. Create a Model for Blacklisted Tokens

First, create a Mongoose model to store the blacklisted tokens.

const mongoose = require('mongoose');

const BlacklistedTokenSchema = new mongoose.Schema({
tokenId: String, // The unique identifier for the JWT token
createdAt: { type: Date, default: Date.now },
});

const BlacklistedToken = mongoose.model('BlacklistedToken', BlacklistedTokenSchema);
module.exports = BlacklistedToken;
  1. Blacklist Token After Logout

When the user logs out, add the token ID to the blacklist.

const jwt = require('jsonwebtoken');
const BlacklistedToken = require('./models/BlacklistedToken');

function blacklistToken(token) {
const decoded = jwt.decode(token);
const tokenId = decoded.jti; // Using the "jti" claim as a unique identifier

const blacklistedToken = new BlacklistedToken({ tokenId });
blacklistedToken.save();
}

// Example of logging out and blacklisting the token
app.post('/logout', (req, res) => {
const token = req.headers['authorization'].split(' ')[1];
blacklistToken(token);
res.send('Logged out successfully');
});
  1. Check for Blacklisted Tokens

When processing requests, check if the token is blacklisted.

async function isTokenBlacklisted(token) {
const decoded = jwt.decode(token);
const tokenId = decoded.jti;

const blacklistedToken = await BlacklistedToken.findOne({ tokenId });

return !!blacklistedToken;
}

app.get('/protected', async (req, res) => {
const token = req.headers['authorization'].split(' ')[1];

// Check if the token is blacklisted
if (await isTokenBlacklisted(token)) {
return res.status(401).send('Token is blacklisted');
}

// Process the request if the token is not blacklisted
res.send('Protected data');
});

Using Redis for Token Blacklisting

Redis is an ideal solution for managing blacklisted tokens because of its fast in-memory data store capabilities.

  1. Install Redis and Connect it to Node.js
npm install redis express jsonwebtoken
  1. Set Up Redis Client
const redis = require('redis');
const client = redis.createClient();
client.on('connect', () => console.log('Connected to Redis'));
  1. Blacklist a Token in Redis

When a user logs out or when their token needs to be invalidated, you can store the token in Redis with a time-to-live (TTL) value to ensure that tokens are not blacklisted forever.

function blacklistTokenInRedis(token) {
const decoded = jwt.decode(token);
const tokenId = decoded.jti; // Using the "jti" claim as a unique identifier

client.setex(tokenId, 3600, 'blacklisted'); // Set token ID with a TTL of 1 hour
}

// Example of logging out and blacklisting the token
app.post('/logout', (req, res) => {
const token = req.headers['authorization'].split(' ')[1];
blacklistTokenInRedis(token);
res.send('Logged out successfully');
});
  1. Check if Token is Blacklisted in Redis

When processing a request, check if the token ID is in the blacklist.

function isTokenBlacklistedInRedis(token, callback) {
const decoded = jwt.decode(token);
const tokenId = decoded.jti;

client.get(tokenId, (err, reply) => {
if (err) throw err;
callback(reply !== null);
});
}

app.get('/protected', (req, res) => {
const token = req.headers['authorization'].split(' ')[1];

// Check if the token is blacklisted in Redis
isTokenBlacklistedInRedis(token, (isBlacklisted) => {
if (isBlacklisted) {
return res.status(401).send('Token is blacklisted');
}

// Process the request if the token is not blacklisted
res.send('Protected data');
});
});

6. Best Practices in Token Blacklisting

  • Use Expiry Time for Blacklisted Tokens: Set a time-to-live (TTL) for blacklisted tokens to avoid the blacklist growing indefinitely. For example, a token that was logged out could remain blacklisted for an hour before being automatically removed.
  • Efficient Lookups: Use fast in-memory data stores like Redis for efficient lookups. Avoid storing blacklisted tokens in a traditional relational database if high scalability is needed.
  • Secure Token Storage: Always use secure HTTPS connections to protect tokens during transit. Also, store tokens securely on the client-side (in HttpOnly cookies or localStorage) to prevent unauthorized access.

7. Conclusion

Token blacklisting provides an essential security feature by allowing you to revoke or invalidate tokens before their expiration time. Whether using a database or Redis, blacklisting ensures that compromised or unwanted tokens can be efficiently rejected during requests. By implementing token blacklisting correctly, you can improve your application’s security and protect sensitive data.

Refresh Token Implementation in Node.js

0
full stack development
full stack development

Table of Contents

  1. Introduction to Refresh Tokens
  2. Setting Up the Project
  3. JWT Authentication and Refresh Token Flow
  4. Creating the Routes for Access Token and Refresh Token
  5. Securing the Application
  6. Conclusion

1. Introduction to Refresh Tokens

When using JWTs for authentication, access tokens are typically short-lived (e.g., 1 hour) for security reasons. However, you don’t want users to have to log in again every hour. This is where refresh tokens come in. A refresh token allows the user to obtain a new access token without needing to log in again.

The flow involves:

  • A short-lived access token used for making authenticated requests.
  • A long-lived refresh token used to obtain a new access token when the current one expires.

When the access token expires, the client sends the refresh token to the server to get a new access token.


2. Setting Up the Project

Install Dependencies

If you haven’t already, start by installing the required dependencies:

npm install express jsonwebtoken bcryptjs dotenv

Environment Configuration (.env)

Make sure you have the following keys in your .env file:

JWT_SECRET=your_jwt_secret
JWT_EXPIRATION=3600 # Access Token Expiration in seconds (1 hour)
JWT_REFRESH_SECRET=your_refresh_jwt_secret
JWT_REFRESH_EXPIRATION=86400 # Refresh Token Expiration in seconds (24 hours)

3. JWT Authentication and Refresh Token Flow

The flow works as follows:

  1. Login: After successful login, the server generates two tokens:
    • An access token (short-lived).
    • A refresh token (long-lived).
  2. Access Token Expiration: When the access token expires, the client uses the refresh token to request a new access token.
  3. Refresh Token Rotation: For security, each time a refresh token is used to obtain a new access token, a new refresh token is generated.

4. Creating the Routes for Access Token and Refresh Token

Step 1: Set up JWT Authentication (Generating Tokens)

Here’s the code to generate both the access token and refresh token:

server.js

const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const dotenv = require('dotenv');
const app = express();

dotenv.config();

app.use(express.json());

// Dummy users (replace with a real database in production)
let users = [
{ id: 1, username: 'user1', password: 'password123', role: 'user' },
];

// Generate Access Token
function generateAccessToken(user) {
return jwt.sign(
{ id: user.id, username: user.username, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
}

// Generate Refresh Token
function generateRefreshToken(user) {
return jwt.sign(
{ id: user.id, username: user.username, role: user.role },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '24h' }
);
}

// Login Route - Authenticates user and returns access & refresh tokens
app.post('/login', async (req, res) => {
const { username, password } = req.body;

const user = users.find(u => u.username === username);
if (!user) return res.status(400).send('Invalid credentials');

const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) return res.status(400).send('Invalid credentials');

// Generate Access Token & Refresh Token
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);

res.json({ accessToken, refreshToken });
});

// Route to Refresh Access Token using Refresh Token
app.post('/token', (req, res) => {
const refreshToken = req.body.refreshToken;
if (!refreshToken) return res.status(401).send('Refresh Token Required');

// Validate the refresh token
jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET, (err, user) => {
if (err) return res.status(403).send('Invalid Refresh Token');

// Generate a new access token
const accessToken = generateAccessToken(user);
const newRefreshToken = generateRefreshToken(user); // Rotate refresh token

res.json({ accessToken, refreshToken: newRefreshToken });
});
});

// Secure route example
app.get('/protected', (req, res) => {
const token = req.headers['authorization']?.split(' ')[1]; // Bearer token

if (!token) return res.status(401).send('Access Token Required');

jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).send('Invalid or Expired Token');
res.json({ message: 'Welcome to protected route!', user });
});
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

Step 2: Explanation

  • Login Route (/login):
    • Accepts user credentials.
    • If credentials are valid, it generates both an access token (1 hour expiration) and a refresh token (24 hours expiration).
    • Sends both tokens back to the client.
  • Token Route (/token):
    • This is used when the client wants to refresh their access token.
    • The client sends the refresh token to the server.
    • If the refresh token is valid, the server generates and returns a new access token and refresh token.
  • Protected Route (/protected):
    • A protected route that requires a valid access token.
    • If the access token is valid, the server responds with the protected content.

5. Securing the Application

Refresh Token Storage

  • Client-side: The refresh token should be stored securely on the client side. You can store it in HttpOnly cookies (more secure than localStorage or sessionStorage), which ensures that the token is not accessible via JavaScript but can still be sent with requests automatically.

Token Expiration

  • Access Tokens: Access tokens should have a relatively short expiration time to minimize risk in case they are compromised. A typical expiration time is 1 hour.
  • Refresh Tokens: Refresh tokens are more long-lived (24 hours or more) and should be rotated regularly.

Rotation & Blacklisting

  • When a refresh token is used to obtain a new access token, it’s recommended to rotate the refresh token — generate a new refresh token every time the client refreshes their access token.
  • Implementing a blacklist for refresh tokens (storing invalidated refresh tokens) can help secure your application.

Implement HTTPS

Always run your application over HTTPS in production to protect sensitive data during transmission, including tokens.


6. Conclusion

In this guide, we implemented refresh tokens alongside JWT for maintaining a secure session and preventing frequent logins.

Summary:

  1. Access Token: Short-lived token used for making API requests.
  2. Refresh Token: Long-lived token used to obtain a new access token when the previous one expires.
  3. Token Rotation: Each time a refresh token is used, a new refresh token is generated for security.
  4. Storage: Always store refresh tokens securely, preferably in HttpOnly cookies.
  5. Protection: Make sure your app is served over HTTPS to secure the token exchange.

End-to-End Example: Google OAuth + JWT + WebSocket Auth with Socket.IO

0
full stack development
full stack development

Project Structure

oauth-websocket-app/

├── server.js
├── auth/
│ └── google.js
├── middleware/
│ └── socketAuth.js
├── public/
│ └── index.html
├── .env
├── package.json

Prerequisites

  • Node.js installed
  • Google Developer Console project with OAuth credentials (Client ID and Secret)
  • Install dependencies using:
npm install express socket.io passport passport-google-oauth20 jsonwebtoken dotenv express-session

Step 1: Environment Variables (.env)

GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
JWT_SECRET=your_jwt_secret

Step 2: Google OAuth Setup (auth/google.js)

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/auth/google/callback"
}, (accessToken, refreshToken, profile, done) => {
const user = {
id: profile.id,
name: profile.displayName,
email: profile.emails[0].value
};
done(null, user);
}));

passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((obj, done) => done(null, obj));

Step 3: Socket Middleware (middleware/socketAuth.js)

const jwt = require('jsonwebtoken');

module.exports = function (socket, next) {
const token = socket.handshake.auth.token;

if (!token) {
return next(new Error('Authentication token required'));
}

try {
const user = jwt.verify(token, process.env.JWT_SECRET);
socket.user = user;
next();
} catch (err) {
next(new Error('Invalid token'));
}
};

Step 4: Express + Socket.IO App (server.js)

require('dotenv').config();
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const session = require('express-session');
const passport = require('passport');
const jwt = require('jsonwebtoken');
const socketAuth = require('./middleware/socketAuth');

require('./auth/google');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

app.use(session({ secret: 'keyboard cat', resave: false, saveUninitialized: false }));
app.use(passport.initialize());
app.use(passport.session());

app.use(express.static('public'));

app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));

app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/' }),
(req, res) => {
const token = jwt.sign(req.user, process.env.JWT_SECRET, { expiresIn: '1h' });
res.redirect(`/index.html?token=${token}`);
}
);

io.use(socketAuth);

io.on('connection', (socket) => {
console.log(`User connected: ${socket.user.name}`);

socket.on('chat message', (msg) => {
io.emit('chat message', { user: socket.user.name, message: msg });
});
});

server.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});

Step 5: Frontend (public/index.html)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>OAuth + WebSocket Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<ul id="messages"></ul>
<input id="msg" autocomplete="off" />
<button onclick="send()">Send</button>

<script src="/socket.io/socket.io.js"></script>
<script>
const urlParams = new URLSearchParams(window.location.search);
const token = urlParams.get('token');

if (!token) {
window.location.href = "/auth/google";
}

const socket = io({ auth: { token } });

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

socket.on('chat message', (data) => {
const item = document.createElement('li');
item.textContent = `${data.user}: ${data.message}`;
document.getElementById('messages').appendChild(item);
});

function send() {
const msg = document.getElementById('msg').value;
socket.emit('chat message', msg);
document.getElementById('msg').value = '';
}
</script>
</body>
</html>

Security Recommendations

  • Use HTTPS and WSS in production environments
  • Avoid storing JWTs in localStorage; prefer in-memory or HttpOnly cookies
  • Add input validation and rate-limiting
  • Implement logout and token blacklisting if necessary

Summary

  • OAuth handles authentication and generates a secure JWT
  • JWT is passed to the frontend and used for WebSocket connection
  • Token is verified server-side via middleware before any real-time communication is allowed

Integrating WebSockets with OAuth and SSO Systems in Node.js

0
full stack development
full stack development

Real-time applications often rely on WebSockets for persistent, low-latency communication, but integrating them with traditional OAuth 2.0 or SSO flows poses a unique challenge. HTTP-based flows work well for traditional requests, but WebSocket protocols don’t automatically carry authentication headers or cookies the same way.

In this article, we’ll explore how to integrate OAuth and SSO securely with WebSockets in a Node.js environment, typically using Socket.IO. We’ll use JWTs as the token carrier to bridge OAuth/SSO with real-time communication.


Table of Contents

  1. Why OAuth/SSO Integration with WebSockets Is Hard
  2. High-Level Architecture
  3. OAuth Flow for WebSockets
  4. Implementing OAuth-Based WebSocket Auth (Step-by-Step)
  5. Refreshing Tokens Mid-WebSocket Session
  6. Using Third-Party Identity Providers (Google, GitHub, etc.)
  7. Security Considerations
  8. Best Practices
  9. Conclusion

1. Why OAuth/SSO Integration with WebSockets Is Hard

OAuth and SSO protocols are primarily designed for stateless HTTP requests, not stateful WebSocket connections.

Challenges include:

  • WebSocket handshake is a one-time upgrade request — it can’t refresh headers later
  • No standard way to attach bearer tokens after initial connection
  • Cookies may not work due to CORS restrictions or cross-domain issues

2. High-Level Architecture

Here’s how it typically works:

  1. Client authenticates via OAuth or SSO (Google, GitHub, Okta, etc.)
  2. Server issues a JWT access token
  3. Client connects to WebSocket using that token
  4. Server verifies token and maintains connection

3. OAuth Flow for WebSockets

  1. Redirect to OAuth provider for login (e.g., Google)
  2. Receive access token (JWT)
  3. Store token client-side (securely)
  4. Send token during WebSocket connection
const socket = io('https://yourapp.com', {
auth: {
token: 'access_token_here'
}
});

4. Implementing OAuth-Based WebSocket Auth (Step-by-Step)

1. Server Setup (Express + Socket.IO)

npm install express socket.io jsonwebtoken dotenv passport passport-google-oauth20

2. Configure Passport OAuth

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
}, (accessToken, refreshToken, profile, done) => {
const user = {
id: profile.id,
name: profile.displayName,
email: profile.emails[0].value
};
return done(null, user);
}));

3. Issue JWT After OAuth Success

app.get('/auth/google/callback', passport.authenticate('google', {
failureRedirect: '/login'
}), (req, res) => {
const token = jwt.sign(req.user, process.env.JWT_SECRET, { expiresIn: '1h' });
res.redirect(`/dashboard?token=${token}`);
});

4. Verify Token in WebSocket

const io = require('socket.io')(server);
const jwt = require('jsonwebtoken');

io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) return next(new Error('Token required'));

try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.user = decoded;
next();
} catch (err) {
return next(new Error('Invalid token'));
}
});

5. Refreshing Tokens Mid-Session

WebSocket connections are long-lived, but OAuth tokens expire.

  • Option 1: Disconnect and reconnect with a new token
  • Option 2: Use refresh tokens to fetch new access tokens before expiry
  • Option 3: Use a backend-issued JWT with a longer lifespan (30–60 mins max recommended)

6. Using Third-Party Identity Providers

Popular OAuth/SSO providers like:

  • Google
  • Facebook
  • GitHub
  • Microsoft Azure AD
  • Okta
  • Auth0

All follow the same OAuth2 standard. Once the OAuth flow is done and you get the token, it can be used with your WebSocket as described.


7. Security Considerations

  • Never trust tokens blindly — Always verify signature and claims
  • Validate exp and iat claims
  • Avoid token reuse across devices unless scoped
  • Use HTTPS + WSS for transport layer security
  • Avoid putting access tokens in query strings
  • Enforce rate limiting and schema validation on WebSocket messages

8. Best Practices

  • Use HTTPS + WSS in production
  • Short-lived tokens, 15–60 mins
  • Implement refresh flow on frontend
  • Monitor for token replay or session hijacking
  • Don’t store access tokens in localStorage (use secure cookies or memory)
  • Centralize WebSocket authorization logic (middleware)

9. Conclusion

Integrating WebSockets with OAuth and SSO systems in Node.js requires an extra layer of design and security awareness. Once the user is authenticated using OAuth2 or a federated identity provider, you can issue a secure JWT and leverage that to authenticate WebSocket connections.

This approach allows you to bridge OAuth-based login systems with real-time apps, making your application both modern and secure.

Advanced WebSocket Authentication & Authorization in Node.js

0
full stack development
full stack development

When building secure and scalable real-time apps using WebSockets and Node.js, authentication is only the beginning. To ensure robust security, we need to go further into authorization, token lifecycle management, and architectural safeguards.


Table of Contents

  1. Role-Based WebSocket Authorization
  2. Room-Level Access Control
  3. Token Rotation & Refresh Strategies
  4. Managing Sessions Across Devices
  5. Security Design Patterns for WebSockets
  6. Common Pitfalls & How to Avoid Them
  7. Final Thoughts

1. Role-Based WebSocket Authorization

Authentication checks who the user is. Authorization checks what they’re allowed to do.

Example: Restrict Admin-Only Rooms

io.on('connection', (socket) => {
if (socket.user.role !== 'admin') {
socket.emit('unauthorized', 'Admins only');
return socket.disconnect();
}

socket.on('admin:task', (data) => {
// Handle admin task
});
});

Best Practice

Always perform authorization checks at the event level. Don’t rely solely on the initial connection verification.


2. Room-Level Access Control

Rooms in Socket.IO group users for targeted communication. But not everyone should access every room.

Secure Room Join Example

socket.on('join-room', (roomId) => {
const allowedRooms = socket.user.allowedRooms || [];

if (allowedRooms.includes(roomId)) {
socket.join(roomId);
} else {
socket.emit('unauthorized', 'You are not allowed to join this room');
}
});

Tip:

Use middleware or decorators (in frameworks like Nest.js) to centralize access logic.


3. Token Rotation & Refresh Strategies

For long-lived WebSocket connections, tokens may expire mid-session.

Two Common Approaches:

a. Reconnect with a New Token

socket.on('token-expired', () => {
const newToken = getNewToken(); // via refresh endpoint
socket.auth.token = newToken;
socket.disconnect().connect(); // reconnect with new token
});

b. Custom ‘refresh’ Event

If you’re using a custom protocol:

socket.emit('refresh-token', oldToken);

The server verifies and returns a new JWT.


4. Managing Sessions Across Devices

To prevent abuse (like sharing tokens), implement:

  • Per-device tokens
  • Session identifiers
  • Blacklists for revoked tokens

Optional: Store token metadata in Redis for centralized session tracking

const redisClient = require('redis').createClient();
redisClient.set(`session:${userId}`, token);

On each connection, verify the token matches the latest active session.


5. Security Design Patterns for WebSockets

a. Namespace Segregation

Use Socket.IO namespaces for internal vs. public services.

const adminIO = io.of('/admin');

Restrict based on role during namespace connection.


b. Per-Event Schema Validation

Use AJV (Another JSON Validator) or similar libraries to validate message payloads.

const Ajv = require("ajv");
const ajv = new Ajv();

const schema = { type: "object", properties: { msg: { type: "string" } }, required: ["msg"] };

socket.on('chat', (data) => {
const valid = ajv.validate(schema, data);
if (!valid) {
return socket.emit('error', 'Invalid message format');
}
});

c. Rate Limiting

Prevent abuse of socket messages.

// Use packages like `socket.io-rate-limiter` or a custom Redis rate limiter

6. Common Pitfalls & How to Avoid Them

PitfallSolution
Exposing token in query paramsUse auth object, not query
No authorization for room joinsValidate permissions before joining
Token doesn’t expireAlways set exp claim in JWT
Reuse of revoked tokensUse blacklist or session store
No validation for incoming payloadsUse JSON schema validators

7. Final Thoughts

Security is not a one-time task—it’s a continuous discipline. For real-time systems, it’s especially critical to protect:

  • Who connects
  • What they can access
  • How long they can remain trusted

Combine JWT, robust role-based policies, payload validation, and secure architecture to build scalable, secure WebSocket-powered applications.