JWT Authentication in a MERN Stack App – Complete Guide
In today’s digital era, securing your web applications is more important than ever. One of the most widely used methods for implementing authentication in modern web apps is JWT (JSON Web Tokens). This article will walk you through implementing JWT authentication in a MERN stack application, i.e., using MongoDB, Express.js, React, and Node.js.
🔐 What is JWT?
JWT (JSON Web Token) is a compact, URL-safe means of representing claims between two parties. A JWT consists of three parts:
- Header – contains the type of token and the hashing algorithm.
- Payload – contains the claims (data).
- Signature – ensures that the token wasn’t altered.
A sample JWT looks like this:xxxxx.yyyyy.zzzzz
🧱 MERN Stack Overview
- MongoDB: NoSQL database.
- Express.js: Web framework for Node.js.
- React: Front-end JavaScript library.
- Node.js: JavaScript runtime for server-side.
JWT will be used for stateless authentication between the front-end (React) and back-end (Node/Express).
📁 Folder Structure
arduinoCopyEditmern-auth/
├── backend/
│ ├── config/
│ ├── middleware/
│ ├── models/
│ ├── routes/
│ └── server.js
├── frontend/
│ ├── public/
│ └── src/
🖥️ Backend – Node.js & Express
Step 1: Install Dependencies
bashCopyEditnpm install express mongoose dotenv bcryptjs jsonwebtoken cors
Step 2: Setup User Model (models/User.js)
jsCopyEditconst mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true }
});
module.exports = mongoose.model('User', UserSchema);
Step 3: Auth Routes (routes/auth.js)
jsCopyEditconst express = require('express');
const router = express.Router();
const User = require('../models/User');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
// Register
router.post('/register', async (req, res) => {
const { name, email, password } = req.body;
try {
let user = await User.findOne({ email });
if (user) return res.status(400).json({ msg: 'User already exists' });
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
user = new User({ name, email, password: hashedPassword });
await user.save();
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token, user: { id: user._id, name, email } });
} catch (err) {
res.status(500).send('Server error');
}
});
// Login
router.post('/login', async (req, res) => {
const { email, password } = req.body;
try {
let user = await User.findOne({ email });
if (!user) return res.status(400).json({ msg: 'User not found' });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(400).json({ msg: 'Invalid credentials' });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token, user: { id: user._id, name: user.name, email: user.email } });
} catch (err) {
res.status(500).send('Server error');
}
});
Step 4: Middleware – Auth (middleware/auth.js)
jsCopyEditconst jwt = require('jsonwebtoken');
function auth(req, res, next) {
const token = req.header('x-auth-token');
if (!token) return res.status(401).json({ msg: 'No token, authorization denied' });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (e) {
res.status(400).json({ msg: 'Token is not valid' });
}
}
module.exports = auth;
Step 5: Protected Route Example
jsCopyEditrouter.get('/user', auth, async (req, res) => {
const user = await User.findById(req.user.id).select('-password');
res.json(user);
});
🌐 Frontend – React
Step 1: Setup Axios
bashCopyEditnpm install axios
jsCopyEdit// utils/axios.js
import axios from 'axios';
const API = axios.create({
baseURL: 'http://localhost:5000',
});
API.interceptors.request.use((req) => {
const token = localStorage.getItem('token');
if (token) {
req.headers['x-auth-token'] = token;
}
return req;
});
export default API;
Step 2: Register & Login Component
jsCopyEdit// Login.js
import React, { useState } from 'react';
import API from './utils/axios';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async () => {
try {
const res = await API.post('/api/auth/login', { email, password });
localStorage.setItem('token', res.data.token);
alert('Login Successful');
} catch (err) {
console.error(err.response.data);
}
};
return (
<div>
<input type="email" value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
<button onClick={handleLogin}>Login</button>
</div>
);
};
export default Login;
Step 3: Access Protected Route
jsCopyEdit// Dashboard.js
import React, { useEffect, useState } from 'react';
import API from './utils/axios';
const Dashboard = () => {
const [user, setUser] = useState(null);
useEffect(() => {
API.get('/api/auth/user')
.then((res) => setUser(res.data))
.catch((err) => console.log(err.response.data));
}, []);
return <div>{user ? `Welcome ${user.name}` : 'Loading...'}</div>;
};
export default Dashboard;
✅ Final Thoughts
JWT authentication in a MERN stack provides a clean, scalable way to secure your application without using sessions. It’s perfect for single-page applications where stateless communication is ideal.
🔄 Summary:
- Used JWT for secure stateless authentication.
- Implemented protected routes.
- Stored tokens on the client-side.
- Verified token on each API call.