Learn to implement secure authentication and authorization in Node.js applications using JWT, password hashing, and security best practices.
Learn to implement secure authentication and authorization in Node.js applications using JWT, password hashing, and security best practices.
Implement JSON Web Token (JWT) authentication for secure user sessions
Content by: Bansi Patel
Node.js Developer
JSON Web Tokens (JWT) are a compact, URL-safe means of representing claims to be transferred between two parties. They are commonly used for authentication and authorization in web applications.
// Install dependencies
npm install jsonwebtoken bcryptjs
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
// Generate JWT token
const generateToken = (userId) => {
return jwt.sign({ userId }, JWT_SECRET, { expiresIn: '24h' });
};
// Verify JWT token
const verifyToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
throw new Error('Invalid token');
}
};
// Login endpoint
app.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
// Find user by email
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate token
const token = generateToken(user._id);
res.json({ token, user: { id: user._id, email: user.email, name: user.name } });
} catch (error) {
res.status(500).json({ error: 'Login failed' });
}
});
// Protected route middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const decoded = verifyToken(token);
req.userId = decoded.userId;
next();
} catch (error) {
return res.status(403).json({ error: 'Invalid token' });
}
};
// Protected route
app.get('/profile', authenticateToken, async (req, res) => {
try {
const user = await User.findById(req.userId).select('-password');
res.json(user);
} catch (error) {
res.status(500).json({ error: 'Failed to get profile' });
}
});
Test your understanding of this topic:
Learn to securely hash and verify passwords using bcrypt
Content by: Kulin Rangani
Node.js Developer
Password hashing is a crucial security practice that converts plain text passwords into irreversible hash values. This prevents passwords from being stored in plain text and protects user accounts even if the database is compromised.
const bcrypt = require('bcryptjs');
// Hash password
const hashPassword = async (password) => {
const saltRounds = 12;
const hashedPassword = await bcrypt.hash(password, saltRounds);
return hashedPassword;
};
// Verify password
const verifyPassword = async (password, hashedPassword) => {
const isValid = await bcrypt.compare(password, hashedPassword);
return isValid;
};
// Registration endpoint
app.post('/register', async (req, res) => {
try {
const { name, email, password } = req.body;
// Check if user already exists
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({ error: 'User already exists' });
}
// Hash password
const hashedPassword = await hashPassword(password);
// Create new user
const user = new User({
name,
email,
password: hashedPassword
});
await user.save();
// Generate token
const token = generateToken(user._id);
res.status(201).json({
message: 'User created successfully',
token,
user: { id: user._id, email: user.email, name: user.name }
});
} catch (error) {
res.status(500).json({ error: 'Registration failed' });
}
});
// Change password endpoint
app.put('/change-password', authenticateToken, async (req, res) => {
try {
const { currentPassword, newPassword } = req.body;
// Get user
const user = await User.findById(req.userId);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
// Verify current password
const isValidPassword = await verifyPassword(currentPassword, user.password);
if (!isValidPassword) {
return res.status(400).json({ error: 'Current password is incorrect' });
}
// Hash new password
const hashedNewPassword = await hashPassword(newPassword);
// Update password
user.password = hashedNewPassword;
await user.save();
res.json({ message: 'Password updated successfully' });
} catch (error) {
res.status(500).json({ error: 'Failed to update password' });
}
});
Test your understanding of this topic:
Learn essential security best practices for Node.js applications
Content by: Prakash Patel
Node.js Developer
// Install security dependencies
npm install helmet express-rate-limit cors
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
// Security middleware
app.use(helmet());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use('/api/', limiter);
// CORS configuration
const corsOptions = {
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
// Input validation middleware
const validateInput = (req, res, next) => {
const { name, email, password } = req.body;
// Sanitize inputs
req.body.name = name?.trim().replace(/[<>]/g, '');
req.body.email = email?.toLowerCase().trim();
// Validate email
const emailRegex = /^[^s@]+@[^s@]+.[^s@]+$/;
if (!emailRegex.test(req.body.email)) {
return res.status(400).json({ error: 'Invalid email format' });
}
// Validate password strength
if (password && password.length < 8) {
return res.status(400).json({ error: 'Password must be at least 8 characters long' });
}
next();
};
// Apply validation to routes
app.post('/register', validateInput, async (req, res) => {
// Registration logic
});
// Environment variables
require('dotenv').config();
const config = {
port: process.env.PORT || 3000,
mongoUri: process.env.MONGO_URI,
jwtSecret: process.env.JWT_SECRET,
nodeEnv: process.env.NODE_ENV || 'development'
};
// Error handling for security
app.use((err, req, res, next) => {
console.error(err.stack);
// Don't leak error details in production
if (process.env.NODE_ENV === 'production') {
res.status(500).json({ error: 'Something went wrong!' });
} else {
res.status(500).json({ error: err.message });
}
});
Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Continue to Module 5