Chapter 4.1☕ 22 min read

JWT Authentication — Hotel Login System

Authentication is like a hotel entry system — only guests with a valid room card (JWT) can enter the restaurant and order biryani.

01What is Auth? — Hotel Ka Entry System

Authentication (Auth) is the process of verifying who a user is. Think of it like a hotel entry system:

  • Registration — User signs up (like checking into the hotel and getting a room)
  • Login — User provides credentials (like showing your ID at the front desk)
  • Token — Server gives a JWT (like a room key card that proves you're a guest)
  • Protected Routes — Only users with a valid JWT can access certain endpoints (like only guests with a room key can enter the hotel restaurant)

Why do we need authentication?

Imagine Swiggy without login — anyone could cancel anyone's order, see anyone's address, or place fake orders. Authentication ensures:

  • Only you can view your orders
  • Only you can place orders from your account
  • Your delivery address is private
  • Your payment details are protected

JWT (JSON Web Token) is a stateless authentication method. The server creates a signed token containing user info (like user ID, email). The client stores this token and sends it with every request. The server verifies the signature to trust the token — no need to store sessions in a database!

In this chapter, we'll build a complete auth system using jsonwebtoken and bcryptjs. By the end, you'll have a working login system where only authenticated users can order biryani.

02Hashing Passwords with bcryptjs — Password Ko Lock Karna

Never store plain text passwords in your database! If your database gets hacked, every user's password is exposed. Always hash passwords before storing them.

bcryptjs is a password hashing library. It uses a technique called salting — it adds random data to each password before hashing, so even if two users have the same password, their hashes are different.

npm install bcryptjs

How bcryptjs works:

  • Hash: Takes plain password + salt rounds → returns hashed string
  • Compare: Takes plain password + stored hash → returns true/false
  • Salt rounds: Higher = more secure but slower. 10-12 is standard.
// authController.js — Password Hashing with bcryptjs
import bcrypt from 'bcryptjs';

const SALT_ROUNDS = 12; // 12 rounds — like 12 layers of biryani masala

// Hash password before saving to DB
const hashPassword = async (plainPassword) => {
  const salt = await bcrypt.genSalt(SALT_ROUNDS);
  const hashedPassword = await bcrypt.hash(plainPassword, salt);
  return hashedPassword;
};

// Compare password during login
const comparePassword = async (plainPassword, hashedPassword) => {
  const isMatch = await bcrypt.compare(plainPassword, hashedPassword);
  return isMatch; // true or false
};

// Example usage
const hashed = await hashPassword('mypassword123');
console.log('Hashed:', hashed);
// $2a$12$LJ3m... (62 characters long!)

const match = await comparePassword('mypassword123', hashed);
console.log('Match:', match); // true

const wrong = await comparePassword('wrongpassword', hashed);
console.log('Wrong:', wrong); // false

Important: bcryptjs hashes are one-way. You cannot reverse a hash back to the original password. That's why we use bcrypt.compare() — it hashes the input and compares with the stored hash.

Salt rounds analogy: Imagine making biryani. More salt rounds = more layers of masala. 10 rounds means the masala is layered 10 times. Each layer makes it harder to reverse-engineer the recipe. But more rounds = more cooking time. 12 rounds is the sweet spot for security vs performance.

03Generating JWT Tokens — Customer ko VIP Pass Dena

JWT (JSON Web Token) is like a VIP pass for your hotel restaurant. Once the user logs in, the server creates a signed token that contains the user's identity. The client stores this token (usually in localStorage or httpOnly cookie) and sends it with every request.

npm install jsonwebtoken

JWT Structure: A JWT has three parts separated by dots:

  • Header — Contains algorithm (HS256) and token type (JWT)
  • Payload — Contains user data (id, email, role) and metadata (iat, exp)
  • Signature — Verifies the token hasn't been tampered with

Example JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsImVtYWlsIjoiYSJ9.t8n5A

// authController.js — JWT Generation
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-do-not-use-in-production';

// Generate JWT token for a user
const generateToken = (user) => {
  const payload = {
    id: user._id,
    email: user.email,
    role: user.role || 'customer'
  };

  const token = jwt.sign(payload, JWT_SECRET, {
    expiresIn: '7d' // Token expires in 7 days
  });

  return token;
};

// Generate on login
const loginUser = async (req, res) => {
  const { email, password } = req.body;

  // Find user in DB
  const user = await User.findOne({ email });
  if (!user) {
    return res.status(401).json({ message: 'Invalid email or password' });
  }

  // Compare password
  const isMatch = await bcrypt.compare(password, user.password);
  if (!isMatch) {
    return res.status(401).json({ message: 'Invalid email or password' });
  }

  // Generate token
  const token = generateToken(user);

  res.json({
    message: 'Login successful! 🍔 Biryani order kar sakte ho!',
    token,
    user: { id: user._id, email: user.email, name: user.name }
  });
};

JWT payload fields:

FieldDescriptionStandard?
idUser's unique ID from databaseCustom
emailUser's emailCustom
roleUser role (admin, customer, restaurant)Custom
iatIssued at — timestamp when token was created✅ Standard
expExpiration — timestamp when token expires✅ Standard

Never put sensitive info (passwords, credit cards) in JWT payload! JWTs are base64-encoded, not encrypted. Anyone can decode and read the payload.

04Protecting Routes — Sirf Logged-in Users Ko Biryani Milegi

Now that users can login and get a JWT, we need to protect routes — only users with a valid JWT can access certain endpoints.

Protect middleware sits between the request and the route handler. It checks the JWT, and if valid, lets the request through. If not, it returns 401 Unauthorized.

// middleware/auth.middleware.js — JWT Protection Middleware
import jwt from 'jsonwebtoken';

const JWT_SECRET = process.env.JWT_SECRET;

const protect = (req, res, next) => {
  // 1. Get token from Authorization header
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      message: 'No token provided! Pehle login karo bhai! 🎫'
    });
  }

  // 2. Extract the token (remove 'Bearer ' prefix)
  const token = authHeader.split(' ')[1];

  try {
    // 3. Verify the token
    const decoded = jwt.verify(token, JWT_SECRET);

    // 4. Attach user to request object
    req.user = decoded;

    // 5. Call next middleware/route handler
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({
        message: 'Token expired! Phirse login karo bhai! Token khatam ho gaya ⏰'
      });
    }
    return res.status(401).json({
      message: 'Invalid token! JWT mein gadbad hai bhai! ❌'
    });
  }
};

// Role-based authorization (optional)
const authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        message: 'Aapke paas permission nahi hai bhai! Sirf ' + roles.join(', ') + ' access kar sakte hain 🚫'
      });
    }
    next();
  };
};

export { protect, authorize };

Using the protect middleware in routes:

// routes/order.routes.js — Protected Routes
import express from 'express';
import { protect, authorize } from '../middleware/auth.middleware.js';
import { placeOrder, getMyOrders } from '../controllers/order.controller.js';

const router = express.Router();

// ❌ Public routes (no auth needed)
router.get('/menu', getMenu);  // Anyone can see the menu

// ✅ Protected routes (must be logged in)
router.post('/orders', protect, placeOrder);  // Only logged-in users can order
router.get('/orders/me', protect, getMyOrders);  // Only my orders

// ✅ Admin-only routes (must be admin role)
router.delete('/orders/:id', protect, authorize('admin'), deleteOrder);

// In server.js:
// app.use('/api', orderRoutes);

How it works in practice:

# Client sends JWT in Authorization header
curl -X POST http://localhost:3000/api/orders \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"items": [{"name": "Chicken Biryani", "qty": 2}]}'

# Without token — 401 error
curl http://localhost:3000/api/orders/me
# Response: { "message": "No token provided! Pehle login karo bhai! 🎫" }
05Complete Auth Flow — Login karo, JWT lo, Order karo

Let's put it all together — a complete auth flow from registration to ordering biryani.

// controllers/auth.controller.js — Complete Auth Controller
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import User from '../models/User.js';

const JWT_SECRET = process.env.JWT_SECRET;

// POST /api/auth/register
export const register = async (req, res) => {
  try {
    const { name, email, password } = req.body;

    // Check if user exists
    const existingUser = await User.findOne({ email });
    if (existingUser) {
      return res.status(400).json({ message: 'User already exists! Yeh email already registered hai' });
    }

    // Hash password
    const salt = await bcrypt.genSalt(12);
    const hashedPassword = await bcrypt.hash(password, salt);

    // Create user
    const user = await User.create({
      name,
      email,
      password: hashedPassword
    });

    // Generate JWT
    const token = jwt.sign(
      { id: user._id, email: user.email, role: user.role },
      JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.status(201).json({
      message: 'Registration successful! Swagat hai bhai! 🎉',
      token,
      user: { id: user._id, name: user.name, email: user.email }
    });
  } catch (error) {
    res.status(500).json({ message: 'Server error: ' + error.message });
  }
};

// POST /api/auth/login
export const login = async (req, res) => {
  try {
    const { email, password } = req.body;

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

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

    // Generate JWT
    const token = jwt.sign(
      { id: user._id, email: user.email, role: user.role },
      JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.json({
      message: 'Login successful! Biryani ka order ready hai! 🍔',
      token,
      user: { id: user._id, name: user.name, email: user.email }
    });
  } catch (error) {
    res.status(500).json({ message: 'Server error: ' + error.message });
  }
};

// GET /api/auth/me — Get current user profile (protected)
export const getMe = async (req, res) => {
  try {
    const user = await User.findById(req.user.id).select('-password');
    res.json({ user });
  } catch (error) {
    res.status(500).json({ message: 'Server error: ' + error.message });
  }
};
// models/User.js — User Schema
import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
  name: { type: String, required: [true, 'Name is required'] },
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    lowercase: true
  },
  password: {
    type: String,
    required: [true, 'Password is required'],
    minlength: [6, 'Password must be at least 6 characters'],
    select: false // Never return password in queries by default
  },
  role: {
    type: String,
    enum: ['customer', 'admin', 'restaurant'],
    default: 'customer'
  }
}, { timestamps: true });

export default mongoose.model('User', userSchema);

Complete Auth Flow Diagram:

  1. User POSTs to /api/auth/register with name, email, password
  2. Server hashes password with bcryptjs (12 salt rounds)
  3. Server saves user to MongoDB (hashed password only!)
  4. Server generates JWT with user id, email, role (expires in 7 days)
  5. Server returns JWT to client
  6. Client stores JWT (localStorage/sessionStorage)
  7. User wants to order biryani — sends JWT in Authorization header
  8. Server's protect middleware verifies JWT signature
  9. If valid → route handler runs → order placed! 🎉
  10. If invalid/expired → 401 Unauthorized → login again!

Key Takeaways

  • ✅ Authentication verifies who a user is — like a hotel entry system with room key cards
  • ✅ Never store plain text passwords — always hash with bcryptjs (12 salt rounds recommended)
  • ✅ bcrypt.hash() is one-way — use bcrypt.compare() to check passwords
  • ✅ JWT = JSON Web Token = stateless auth token with three parts: header.payload.signature
  • ✅ Generate JWT on login: jwt.sign(payload, secret, { expiresIn })
  • ✅ Protect middleware checks JWT: jwt.verify(token, secret) and attaches user to req.user
  • ✅ Never put sensitive data (passwords) in JWT payload — it is base64-encoded, not encrypted
  • ✅ Always store JWT_SECRET in .env file, never hardcode it
  • ✅ Use role-based authorization: authorize('admin') for admin-only routes
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase