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.
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.
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.
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:
| Field | Description | Standard? |
|---|---|---|
id | User's unique ID from database | Custom |
email | User's email | Custom |
role | User role (admin, customer, restaurant) | Custom |
iat | Issued at — timestamp when token was created | ✅ Standard |
exp | Expiration — 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.
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! 🎫" }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:
- User POSTs to
/api/auth/registerwith name, email, password - Server hashes password with bcryptjs (12 salt rounds)
- Server saves user to MongoDB (hashed password only!)
- Server generates JWT with user id, email, role (expires in 7 days)
- Server returns JWT to client
- Client stores JWT (localStorage/sessionStorage)
- User wants to order biryani — sends JWT in Authorization header
- Server's protect middleware verifies JWT signature
- If valid → route handler runs → order placed! 🎉
- 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
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login