Understanding JWT: Structure, Use Cases, Best Practices, and Implementation in Node.js with TypeScript & Mongo DB
Parathan Thiyagalingam
Software Engineer | Full Stack Developer | AWS Community Builder | Educator & Content Creator | Academic | Tech Enthusiast
JSON Web Tokens (JWT) are a popular way to handle authentication in modern web applications. In this blog post, we'll dive deep into JWTs, discussing their structure, use cases, best practices, and how to issue and verify them with a practical example in Node.js using TypeScript. and Mongo DB as database.
What is JWT?
JWT is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.
Structure of a JWT
A JWT consists of three parts separated by dots (.):
Header
The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA.
Example:
{
"alg": "HS256",
"typ": "JWT"
}
Payload
The payload contains the claims. Claims are statements about an entity (typically, the user) and additional metadata. There are three types of claims: registered, public, and private.
Example:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Signature
To create the signature part, you have to take the encoded header, the encoded payload, a secret, and the algorithm specified in the header, and sign that.
For example, if you want to use the HMAC SHA256 algorithm, the signature will be created in the following way:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
Use Cases for JWT
Best Practices
Issuing and Verifying JWTs in Node.js with TypeScript
Setup
First, let's set up a Node.js project with TypeScript.
mkdir jwt-nodejs-sample
cd jwt-example
npm init -y
2. Install dependencies:
npm install express jsonwebtoken bcrypt mongoose
npm install --save-dev typescript @types/node @types/express @types/jsonwebtoken @types/bcrypt ts-node-dev @types/mongoose
3. Initialise TypeScript:
npx tsc --init
4. Configure TypeScript (tsconfig.json):
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "./src"
}
}
Create the following directory structure:
src/
├── controllers/
│ └── authController.ts
├── middlewares/
│ └── authMiddleware.ts
├── models/
│ └── User.ts
├── routes/
│ └── authRoutes.ts
├── types/
│ └── express/index.d.ts
└── index.ts
Let's create User Model
src/models/User.ts:
import { Schema, model, Document, Model, Types } from 'mongoose';
import bcrypt from 'bcrypt';
interface IUser extends Document {
_id: Types.ObjectId;
username: string;
email: string;
password: string;
comparePassword(candidatePassword: string): Promise<boolean>;
}
const userSchema = new Schema<IUser>({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
});
userSchema.pre<IUser>('save', async function (next) {
if (!this.isModified('password')) {
return next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
userSchema.methods.comparePassword = async function (candidatePassword: string): Promise<boolean> {
return bcrypt.compare(candidatePassword, this.password);
};
const User: Model<IUser> = model<IUser>('User', userSchema);
export { User, IUser };
Let's create Authentication Middleware
src/middlewares/authMiddleware.ts:
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { User, IUser } from '../models/user';
import { Types } from 'mongoose';
export interface AuthRequest extends Request {
user?: IUser;
}
export const authMiddleware = async (req: AuthRequest, res: Response, next: NextFunction) => {
const token = req.cookies.jwt;
if (!token) {
return res.status(401).json({ message: 'No token, authorization denied' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { id: Types.ObjectId };
const user = await User.findById(decoded.id).select('-password');
if (!user) {
return res.status(401).json({ message: 'Token is not valid' });
}
(req as AuthRequest).user = user;
next();
} catch (err) {
res.status(401).json({ message: 'Token is not valid' });
}
};
Let's create Authentication Controller
src/controllers/authController.ts:
import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import { User, IUser } from '../models/user';
import { Types } from 'mongoose';
import { AuthRequest } from '../midlewares/authMiddleware';
const generateToken = (id: Types.ObjectId) => {
return jwt.sign({ id }, process.env.JWT_SECRET!, { expiresIn: '1h' });
};
export const register = async (req: Request, res: Response) => {
const { username, email, password } = req.body;
try {
const user: IUser = new User({ username, email, password });
await user.save();
const token = generateToken(user._id);
res.cookie('jwt', token, { httpOnly: true, maxAge: 3600000 });
res.status(201).json({ message: 'User registered successfully', user: { username, email } });
} catch (error) {
const err = error as Error;
res.status(400).json({ message: err.message });
}
};
export const login = async (req: Request, res: Response) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(400).json({ message: 'Invalid credentials' });
}
const token = generateToken(user._id);
res.cookie('jwt', token, { httpOnly: true, maxAge: 3600000 });
res.status(200).json({ message: 'User logged in successfully', user: { username: user.username, email: user.email } });
} catch (error) {
const err = error as Error;
res.status(400).json({ message: err.message });
}
};
export const logout = async (req: Request, res: Response) => {
res.cookie('jwt', '', { httpOnly: true, expires: new Date(0) });
res.status(200).json({ message: 'User logged out successfully' });
};
export const getProfile = async (req: Request, res: Response) => {
try {
const user = (req as AuthRequest).user;
if (!user) {
return res.status(404).json({ message: 'User not found' });
}
res.status(200).json({ user: { username: user.username, email: user.email } });
} catch (error) {
const err = error as Error;
res.status(500).json({ message: err.message });
}
};
Let's create Authentication Routes
src/routes/authRoutes.ts:
import { Router } from 'express';
import { register, login, logout, getProfile } from '../controllers/authController';
import { authMiddleware } from '../midlewares/authMiddleware';
const router = Router();
router.post('/register', register);
router.post('/login', login);
router.get('/logout', authMiddleware, logout);
router.get('/profile', authMiddleware, getProfile);
export default router;
Let's create Main Application File
src/index.ts:
import express from 'express';
import mongoose from 'mongoose';
import dotenv from 'dotenv';
import cookieParser from 'cookie-parser';
import authRoutes from './routes/authRoutes';
dotenv.config();
const app = express();
const PORT = process.env.PORT || 5000;
app.use(express.json());
app.use(cookieParser());
app.use('/api/auth', authRoutes);
mongoose.connect(process.env.MONGO_URI!)
.then(() => {
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
})
.catch(err => console.error(err));
JWT is a powerful tool for handling authentication and authorization in web applications. Understanding its structure, use cases, and best practices can help you implement secure and efficient authentication mechanisms. With the practical example provided, you should be able to set up JWT-based authentication in a Node.js application using TypeScript. Remember to always validate tokens, use HTTPS, and keep your signing keys secure.
Connect with me on LinkedIn. Thanks for reading
Software Engineer | Full Stack Developer | AWS Community Builder | Educator & Content Creator | Academic | Tech Enthusiast
8 个月AVA? - An Orange Education Label May I know what made you feel Funny on this? ??