🏠 Home / Hub

🟢 Node.js Lesson 07 — JWT Authentication

← Back to Node.js Menu  |  🏠 Hub

1. JWT ဆိုတာ ဘာလဲ?

JWT (JSON Web Token) = user identity ကို token ထဲ encode ပြီး stateless auth လုပ်ခြင်း
Header.Payload.Signature — 3 parts, base64url encoded
Server မှာ session store မလို — token ကိုပဲ verify လုပ်
# Install packages
npm install jsonwebtoken bcryptjs express dotenv

# .env
JWT_SECRET=your_super_secret_key_min_32_chars_long
JWT_EXPIRES=7d
BCRYPT_ROUNDS=12

# Structure
src/
  middleware/
    auth.js         ← protect routes
  routes/
    auth.js         ← login/register/refresh
    users.js        ← protected routes
  utils/
    jwt.js          ← token helpers

2. Password Hashing (bcrypt)

// utils/password.js
const bcrypt = require('bcryptjs');

const ROUNDS = parseInt(process.env.BCRYPT_ROUNDS) || 12;

async function hashPassword(plain) {
  return bcrypt.hash(plain, ROUNDS);
}

async function verifyPassword(plain, hashed) {
  return bcrypt.compare(plain, hashed);
}

module.exports = { hashPassword, verifyPassword };

// Usage
const hashed = await hashPassword("secret123");
// "$2b$12$xxxxxxxxxxxxxxxxxxxxxxxxxxxx" — 60 chars, includes salt
const ok = await verifyPassword("secret123", hashed);  // true
const no = await verifyPassword("wrong",     hashed);  // false
⚠️ NEVER store plain text passwords. bcrypt ကိုသာ သုံး. MD5/SHA1 = insecure for passwords

3. JWT Helpers

// utils/jwt.js
const jwt = require('jsonwebtoken');

const SECRET  = process.env.JWT_SECRET;
const EXPIRES = process.env.JWT_EXPIRES || '7d';

function signToken(payload) {
  return jwt.sign(payload, SECRET, { expiresIn: EXPIRES });
}

function verifyToken(token) {
  try {
    return { data: jwt.verify(token, SECRET), error: null };
  } catch (err) {
    const msg = err.name === 'TokenExpiredError'
      ? 'Token expired'
      : 'Invalid token';
    return { data: null, error: msg };
  }
}

function refreshToken(token) {
  const { data, error } = verifyToken(token);
  if (error) return { token: null, error };
  // Re-sign with same payload (minus old iat/exp)
  const { iat, exp, ...payload } = data;
  return { token: signToken(payload), error: null };
}

module.exports = { signToken, verifyToken, refreshToken };

4. Auth Middleware

// middleware/auth.js
const { verifyToken } = require('../utils/jwt');

function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

  const token = authHeader.split(' ')[1];
  const { data, error } = verifyToken(token);

  if (error) {
    return res.status(401).json({ error });
  }

  req.user = data;  // attach user to request
  next();
}

// Role-based auth
function authorize(...roles) {
  return (req, res, next) => {
    if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
}

module.exports = { authenticate, authorize };

5. Auth Routes (Register / Login)

// routes/auth.js
const express  = require('express');
const router   = express.Router();
const { hashPassword, verifyPassword } = require('../utils/password');
const { signToken } = require('../utils/jwt');

// In-memory users (replace with DB)
const users = [];

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

    if (!name || !email || !password) {
      return res.status(400).json({ error: 'All fields required' });
    }
    if (users.find(u => u.email === email)) {
      return res.status(409).json({ error: 'Email already registered' });
    }
    if (password.length < 8) {
      return res.status(400).json({ error: 'Password min 8 characters' });
    }

    const hashed = await hashPassword(password);
    const user   = { id: Date.now(), name, email, password: hashed, role: 'user' };
    users.push(user);

    const token = signToken({ id: user.id, email, role: user.role });
    const { password: _, ...safeUser } = user;  // exclude password

    res.status(201).json({ token, user: safeUser });
  } catch (err) {
    res.status(500).json({ error: 'Registration failed' });
  }
});

// POST /api/auth/login
router.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = users.find(u => u.email === email);

    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const valid = await verifyPassword(password, user.password);
    if (!valid) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const token = signToken({ id: user.id, email, role: user.role });
    const { password: _, ...safeUser } = user;

    res.json({ token, user: safeUser });
  } catch (err) {
    res.status(500).json({ error: 'Login failed' });
  }
});

module.exports = router;

6. Protected Routes

// routes/users.js
const express = require('express');
const router  = express.Router();
const { authenticate, authorize } = require('../middleware/auth');

// Public
router.get('/public', (req, res) => {
  res.json({ message: 'Anyone can see this' });
});

// Protected — need valid JWT
router.get('/profile', authenticate, (req, res) => {
  res.json({ user: req.user, message: 'Your profile' });
});

// Admin only
router.get('/admin', authenticate, authorize('admin'), (req, res) => {
  res.json({ message: 'Admin dashboard' });
});

module.exports = router;

// server.js
const express = require('express');
const app = express();
app.use(express.json());
require('dotenv').config();

app.use('/api/auth',  require('./routes/auth'));
app.use('/api/users', require('./routes/users'));

// Test:
// POST /api/auth/register { name, email, password }
// POST /api/auth/login    { email, password }
// GET  /api/users/profile  Authorization: Bearer <token>
💡 Production: store tokens in httpOnly cookies (not localStorage) to prevent XSS attacks

← Node.js 06  |  Next: Node.js 08 → WebSockets →

📌 Study Checklist