This README file provides a step-by-step guide for implementing JSON Web Token (JWT) authentication in a full-stack application, using a Node.js backend and a Next.js frontend.
JWT (JSON Web Token) is a compact, self-contained way to securely transmit information between parties. It is widely used for authentication and data exchange in web applications.
- Node.js installed
- Basic understanding of Express.js
- A database (e.g., MongoDB, PostgreSQL) for storing user data
- Initialize your Node.js project:
npm init -y
- Install necessary dependencies:
npm install express jsonwebtoken bcryptjs dotenv body-parser cors
Example backend project structure:
backend/
|-- src/
| |-- controllers/
| |-- middleware/
| |-- routes/
| |-- utils/
| |-- app.js
|-- .env
|-- package.json
|-- README.md
Create a utility file for JWT operations (e.g., utils/jwt.js):
import jwt from 'jsonwebtoken';
const SECRET_KEY = process.env.JWT_SECRET || 'your-secret-key';
// Generate a token
export const generateToken = (payload, expiresIn = '1h') => {
return jwt.sign(payload, SECRET_KEY, { expiresIn });
};
// Verify a token
export const verifyToken = (token) => {
try {
return jwt.verify(token, SECRET_KEY);
} catch (error) {
throw new Error('Invalid or expired token');
}
};Create a middleware function to protect routes (e.g., middleware/authMiddleware.js):
import { verifyToken } from '../utils/jwt.js';
export const authenticate = (req, res, next) => {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) {
return res.status(401).json({ message: 'Access token is required' });
}
try {
const decoded = verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ message: 'Invalid or expired token' });
}
};Create an authRoutes.js file:
import express from 'express';
import bcrypt from 'bcryptjs';
import { generateToken } from '../utils/jwt.js';
import { authenticate } from '../middleware/authMiddleware.js';
const router = express.Router();
// Mock user data (use a real database in production)
const users = [];
// Register route
router.post('/register', async (req, res) => {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
users.push({ username, password: hashedPassword });
res.status(201).json({ message: 'User registered successfully' });
});
// Login route
router.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}
const token = generateToken({ username });
res.status(200).json({ token });
});
// Protected route
router.get('/profile', authenticate, (req, res) => {
res.status(200).json({ message: 'Welcome to your profile', user: req.user });
});
export default router;- Create a Next.js project:
npx create-next-app@latest frontend
- Install Axios for API requests:
npm install axios
- Registration:
- Submit user credentials to the
/registerendpoint.
- Submit user credentials to the
- Login:
- Obtain a JWT token upon successful login and store it in a cookie or local storage.
- Access Protected Routes:
- Attach the JWT token in the
Authorizationheader for API requests.
- Attach the JWT token in the
Create a utility file for Axios (e.g., utils/axios.js):
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'http://localhost:5000/api',
});
export const setAuthToken = (token) => {
if (token) {
apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`;
} else {
delete apiClient.defaults.headers.common['Authorization'];
}
};
export default apiClient;Login Component:
'use client';
import { useState } from 'react';
import axios, { setAuthToken } from '../utils/axios';
const Login = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async () => {
try {
const response = await axios.post('/login', { username, password });
const token = response.data.token;
setAuthToken(token);
localStorage.setItem('token', token);
alert('Login successful!');
} catch (error) {
console.error(error);
alert('Login failed');
}
};
return (
<div>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<input
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button onClick={handleLogin}>Login</button>
</div>
);
};
export default Login;Protected Route Component:
'use client';
import { useEffect, useState } from 'react';
import axios from '../utils/axios';
const Profile = () => {
const [profile, setProfile] = useState(null);
useEffect(() => {
const fetchProfile = async () => {
try {
const response = await axios.get('/profile');
setProfile(response.data);
} catch (error) {
console.error('Error fetching profile:', error);
}
};
fetchProfile();
}, []);
if (!profile) return <div>Loading...</div>;
return (
<div>
<h1>{profile.message}</h1>
<pre>{JSON.stringify(profile.user, null, 2)}</pre>
</div>
);
};
export default Profile;- Store the secret key securely: Use environment variables to manage sensitive data.
- Use HTTPS: Ensure tokens are transmitted securely.
- Set token expiration: Use short-lived tokens and refresh them periodically.
- Validate token payload: Avoid storing sensitive data in the token.
- Use secure storage: Prefer HttpOnly cookies for storing tokens over local storage.