DEV Community

Mohin Sheikh
Mohin Sheikh

Posted on

Adding Authentication to an Express.js Application with JWT: A Beginner-Friendly Guide

Image description


📖 Table of Contents

  1. Introduction to Authentication
  2. What is JWT (JSON Web Token)?
  3. Setting Up the Project
  4. Installing Dependencies
  5. Creating a User Model
  6. Setting Up JWT Authentication Middleware
  7. Creating Authentication Routes (Login, Register, Protected Route)
  8. Testing the Authentication System
  9. Environment Variables for Security
  10. Best Practices for Authentication
  11. Conclusion

1. Introduction to Authentication

Authentication ensures that only verified users can access certain resources in your application. In this guide, we’ll use JWT (JSON Web Token) for secure authentication in an Express.js application.


2. What is JWT (JSON Web Token)?

  • JWT is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object.
  • Tokens are signed using a secret key or a public/private key pair.

Structure of a JWT Token:

Header.Payload.Signature
Enter fullscreen mode Exit fullscreen mode
  • Header: Metadata about the token.
  • Payload: User data and claims.
  • Signature: Verifies the integrity of the token.

3. Setting Up the Project

Ensure you have a basic Express.js application ready. If not, follow this guide.

Initialize a new Node.js project:

npm init -y
npm install express jsonwebtoken bcryptjs dotenv
Enter fullscreen mode Exit fullscreen mode

4. Installing Dependencies

  • express: Web framework for Node.js.
  • jsonwebtoken: Handles JWT generation and verification.
  • bcryptjs: Hashes passwords securely.
  • dotenv: Loads environment variables.

Install all dependencies:

npm install express jsonwebtoken bcryptjs dotenv
npm install --save-dev @types/jsonwebtoken @types/bcryptjs
Enter fullscreen mode Exit fullscreen mode

5. Creating a User Model

For MongoDB:

File: src/models/userModel.ts

import mongoose, { Schema, Document } from 'mongoose';

interface IUser extends Document {
  username: string;
  email: string;
  password: string;
}

const UserSchema: Schema = new Schema({
  username: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
});

export default mongoose.model<IUser>('User', UserSchema);
Enter fullscreen mode Exit fullscreen mode

For PostgreSQL:

File: src/models/userModel.ts

import db from '../config/knex';

export const createUser = (user: { username: string; email: string; password: string }) => db('users').insert(user);
export const getUserByEmail = (email: string) => db('users').where({ email }).first();
Enter fullscreen mode Exit fullscreen mode

6. Setting Up JWT Authentication Middleware

File: src/middleware/authMiddleware.ts

import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

interface AuthRequest extends Request {
  user?: any;
}

export const authenticateJWT = (req: AuthRequest, res: Response, next: NextFunction) => {
  const token = req.header('Authorization')?.split(' ')[1];

  if (!token) {
    return res.status(403).json({ message: 'Authorization required' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET || '');
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ message: 'Invalid Token' });
  }
};
Enter fullscreen mode Exit fullscreen mode

7. Creating Authentication Routes (Login, Register, Protected Route)

7.1 Register Route

File: src/controllers/authController.ts

import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import User from '../models/userModel';
import { Request, Response } from 'express';

export const registerUser = async (req: Request, res: Response) => {
  const { username, email, password } = req.body;

  const hashedPassword = await bcrypt.hash(password, 10);

  const newUser = new User({ username, email, password: hashedPassword });
  await newUser.save();

  res.status(201).json({ message: 'User registered successfully' });
};
Enter fullscreen mode Exit fullscreen mode

7.2 Login Route

export const loginUser = async (req: Request, res: Response) => {
  const { email, password } = req.body;

  const user = await User.findOne({ email });
  if (!user) {
    return res.status(400).json({ message: 'Invalid credentials' });
  }

  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) {
    return res.status(400).json({ message: 'Invalid credentials' });
  }

  const token = jwt.sign({ id: user._id, email: user.email }, process.env.JWT_SECRET || '', { expiresIn: '1h' });
  res.json({ token });
};
Enter fullscreen mode Exit fullscreen mode

7.3 Protected Route

export const protectedRoute = async (req: any, res: Response) => {
  res.json({ message: `Hello, ${req.user.email}! You have access.` });
};
Enter fullscreen mode Exit fullscreen mode

7.4 Authentication Routes

File: src/routes/authRoutes.ts

import express from 'express';
import { registerUser, loginUser, protectedRoute } from '../controllers/authController';
import { authenticateJWT } from '../middleware/authMiddleware';

const router = express.Router();

router.post('/register', registerUser);
router.post('/login', loginUser);
router.get('/protected', authenticateJWT, protectedRoute);

export default router;
Enter fullscreen mode Exit fullscreen mode

7.5 Update app.ts

import authRoutes from './routes/authRoutes';

app.use('/api/auth', authRoutes);
Enter fullscreen mode Exit fullscreen mode

8. Testing the Authentication System

8.1 Register a User

  • Endpoint: POST /api/auth/register
  • Request Body:
{
  "username": "testuser",
  "email": "test@example.com",
  "password": "password123"
}
Enter fullscreen mode Exit fullscreen mode
  • Response:
{
  "message": "User registered successfully"
}
Enter fullscreen mode Exit fullscreen mode

8.2 Login User

  • Endpoint: POST /api/auth/login
  • Request Body:
{
  "email": "test@example.com",
  "password": "password123"
}
Enter fullscreen mode Exit fullscreen mode
  • Response:
{
  "token": "JWT_TOKEN_HERE"
}
Enter fullscreen mode Exit fullscreen mode

8.3 Access Protected Route

  • Endpoint: GET /api/auth/protected
  • Header:
Authorization: Bearer JWT_TOKEN_HERE
Enter fullscreen mode Exit fullscreen mode
  • Response:
{
  "message": "Hello, test@example.com! You have access."
}
Enter fullscreen mode Exit fullscreen mode

9. Environment Variables for Security

File: .env

JWT_SECRET=your_jwt_secret_key
Enter fullscreen mode Exit fullscreen mode

Ensure you keep this secret key secure and never expose it publicly.


10. Best Practices for Authentication

  1. Always hash passwords using bcrypt.
  2. Store JWT secrets securely in .env.
  3. Use short expiration times for tokens.
  4. Implement refresh tokens for better security.
  5. Avoid sending sensitive data in JWT payloads.

11. Conclusion

  • Authentication Flow: User registration → Login → JWT Token Generation → Access Protected Routes.
  • JWT is stateless and scalable for modern applications.
  • Secure your routes using middleware.

✨ Your Express.js app now has JWT-based authentication! 🚀


Author: Mohin Sheikh

Follow me on GitHub for more insights!

Top comments (0)