This guide covers the implementation of secure JWT authentication and refresh token flow in a MERN stack application. It is intended for developers who have a basic understanding of the MERN stack and want to add secure authentication to their application. By the end of this guide, you will have a fully functional secure JWT authentication system with refresh tokens, secure cookies, and CSRF protection. The tools and prerequisites needed for this guide include Node.js, Express.js, MongoDB, and React.js. You will achieve a secure authentication system that protects user data and prevents common web vulnerabilities.
Prerequisites & System Requirements
Ensure your development workstation or staging server fulfills the following prerequisites before initiating commands:
Target System Architecture Diagram
Interactive Execution Checklist
Step-by-Step Implementation Guide
Step 1: Set up the Project Structure
Create a new MERN stack project with the required dependencies, including express.js, mongoose, and jsonwebtoken. Initialize a new Node.js project using npm init and install the required dependencies using npm install.
Execute Command Terminal:
npm init -y && npm install express mongoose jsonwebtokenStep 2: Configure MongoDB and Mongoose
Set up a new MongoDB database and connect to it using Mongoose. Create a new Mongoose model for the user collection.
Execute Command Terminal:
const mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/auth', { useNewUrlParser: true, useUnifiedTopology: true }); const userSchema = new mongoose.Schema({ username: String, password: String }); const User = mongoose.model('User', userSchema);Step 3: Implement User Registration
Create a new API endpoint for user registration. Hash the user's password using a secure hashing algorithm like bcrypt.
Execute Command Terminal:
const bcrypt = require('bcrypt'); const registerUser = async (req, res) => { const { username, password } = req.body; const hashedPassword = await bcrypt.hash(password, 10); const user = new User({ username, password: hashedPassword }); await user.save(); res.json({ message: 'User registered successfully' }); };Step 4: Implement User Login
Create a new API endpoint for user login. Verify the user's password using the hashed password stored in the database.
Execute Command Terminal:
const loginUser = async (req, res) => { const { username, password } = req.body; const user = await User.findOne({ username }); if (!user) return res.status(401).json({ message: 'Invalid username or password' }); const isValidPassword = await bcrypt.compare(password, user.password); if (!isValidPassword) return res.status(401).json({ message: 'Invalid username or password' }); const token = jwt.sign({ userId: user._id }, process.env.SECRET_KEY, { expiresIn: '1h' }); res.json({ token }); };Step 5: Implement JWT Access Token and Refresh Token
Create a new API endpoint for generating a new access token using the refresh token.
Execute Command Terminal:
const generateAccessToken = async (req, res) => { const { refreshToken } = req.body; const user = await User.findOne({ refreshToken }); if (!user) return res.status(401).json({ message: 'Invalid refresh token' }); const accessToken = jwt.sign({ userId: user._id }, process.env.SECRET_KEY, { expiresIn: '1h' }); res.json({ accessToken }); };Step 6: Implement Secure Cookies and HttpOnly Cookies
Set the access token and refresh token in secure cookies using the HttpOnly flag.
Execute Command Terminal:
const setSecureCookies = (res, accessToken, refreshToken) => { res.cookie('accessToken', accessToken, { httpOnly: true, secure: true, sameSite: 'strict' }); res.cookie('refreshToken', refreshToken, { httpOnly: true, secure: true, sameSite: 'strict' }); };Step 7: Implement Token Rotation and CSRF Protection
Rotate the access token and refresh token on each request. Verify the CSRF token on each request.
Execute Command Terminal:
const rotateTokens = async (req, res) => { const { accessToken, refreshToken } = req.cookies; const user = await User.findOne({ refreshToken }); if (!user) return res.status(401).json({ message: 'Invalid refresh token' }); const newAccessToken = jwt.sign({ userId: user._id }, process.env.SECRET_KEY, { expiresIn: '1h' }); const newRefreshToken = jwt.sign({ userId: user._id }, process.env.SECRET_KEY, { expiresIn: '30d' }); setSecureCookies(res, newAccessToken, newRefreshToken); };Step 8: Implement Logout Flow
Clear the access token and refresh token cookies on logout.
Execute Command Terminal:
const logoutUser = (res) => { res.clearCookie('accessToken'); res.clearCookie('refreshToken'); };Step 9: Implement RBAC and Protected Routes
Implement role-based access control (RBAC) using middleware functions. Protect routes using the access token and refresh token.
Execute Command Terminal:
const authenticateUser = async (req, res, next) => { const { accessToken } = req.cookies; if (!accessToken) return res.status(401).json({ message: 'Unauthorized' }); const user = await User.findOne({ accessToken }); if (!user) return res.status(401).json({ message: 'Unauthorized' }); req.user = user; next(); };Step 10: Test the Application
Test the application using tools like Postman or cURL. Verify that the access token and refresh token are being generated and rotated correctly.
Execute Command Terminal:
const testApplication = async () => { const response = await axios.post('https://example.com/login', { username: 'test', password: 'test' }); const { accessToken, refreshToken } = response.data; console.log(accessToken, refreshToken); };Step 11: Deploy the Application to Production
Deploy the application to a production environment using tools like Docker or Kubernetes. Verify that the application is working as expected in the production environment.
Execute Command Terminal:
const deployApplication = async () => { const response = await axios.post('https://example.com/deploy', { environment: 'production' }); console.log(response.data); };Pro Tips & Optimizations
Common Pitfalls to Avoid
In this guide, we implemented secure JWT authentication and refresh token flow in a MERN stack application. We used middleware functions to protect routes and rotated the tokens on each request to prevent token fixation attacks. We also tested the application using tools like Postman or cURL and deployed it to a production environment using tools like Docker or Kubernetes. By following the steps outlined in this guide, you can implement secure JWT authentication in your own MERN stack application.
Production Best Practices & Hardening
Security Hardening
Disable root SSH access, enforce key-based auth, and enable UFW firewall on ports 80/443.
Memory Management
Set Node.js max-old-space-size to 80% of total RAM to avoid Linux OOM-killer crashes.
Frequently Asked Questions
Yes, all NGINX, Docker, and PM2 deployment steps can be packaged into Infrastructure as Code (IaC) playbooks.
Need Help Implementing This?
Partner with Inteliny's principal architects to audit your stack, automate CI/CD, and accelerate deployment.