> ## Documentation Index
> Fetch the complete documentation index at: https://docs.loremstock.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Implementing JWT Authentication in Node.js

> Build a complete JWT-based authentication system with registration, login, and token verification middleware in Express using bcrypt, jsonwebtoken, and MySQL with callback-style db.query().

Authentication answers: "Who are you?" This page builds a complete auth system using JWTs backed by MySQL, using the same callback pattern as `db.query()`. You will see register, login, and a `verifyToken` middleware that protects any route.

## Authentication vs Authorization

| Concept            | Question         | Example                                     |
| ------------------ | ---------------- | ------------------------------------------- |
| **Authentication** | Who are you?     | "You are user Alice (ID: 42)"               |
| **Authorization**  | What can you do? | "Alice can read posts but not delete users" |

## Sessions vs JWT

| Feature     | Sessions                                 | JWT                                      |
| ----------- | ---------------------------------------- | ---------------------------------------- |
| Storage     | Server stores in memory/DB               | Client stores token, server is stateless |
| Scalability | Hard (sessions must sync across servers) | Easy (any server validates the token)    |
| Revocation  | Easy (delete from DB)                    | Harder (need blacklist or short expiry)  |
| Use case    | Traditional web apps                     | REST APIs, mobile apps                   |

## What Is a JWT?

A JWT has three base64url parts separated by dots:

```text theme={null}
header.payload.signature
```

| Part          | Content                                                              |
| ------------- | -------------------------------------------------------------------- |
| **Header**    | Algorithm and token type: `{"alg":"HS256","typ":"JWT"}`              |
| **Payload**   | Claims: `{"id":42,"email":"alice@test.com","role":"user","exp":...}` |
| **Signature** | HMACSHA256(header + "." + payload, secretKey)                        |

<Warning>
  The payload is base64 encoded, NOT encrypted. Anyone can decode it. Never put passwords or sensitive data in the payload.
</Warning>

## MySQL Table

```sql theme={null}
CREATE TABLE IF NOT EXISTS users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  email VARCHAR(191) NOT NULL UNIQUE,
  password VARCHAR(255) NOT NULL,
  role ENUM('user', 'admin') DEFAULT 'user',
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```

## config/db.js

```javascript theme={null}
const mysql = require('mysql2');

const db = mysql.createConnection({
  host: process.env.DB_HOST || 'localhost',
  user: process.env.DB_USER || 'root',
  password: process.env.DB_PASS || '',
  database: process.env.DB_NAME || 'swdbd_app'
});

db.connect(function (err) {
  if (err) {
    console.error('MySQL connection failed:', err.message);
    process.exit(1);
  }
  console.log('MySQL connected');
});

module.exports = db;
```

## Install

```bash theme={null}
npm install jsonwebtoken bcrypt mysql2 dotenv
```

## Register Route

```javascript theme={null}
// POST /api/auth/register
app.post('/api/auth/register', function (req, res) {
  const { name, email, password } = req.body;

  if (!name || !email || !password) {
    return res.status(400).json({ message: 'All fields are required' });
  }

  // Check if email already exists
  const sqlCheck = 'SELECT id FROM users WHERE email = ?';
  db.query(sqlCheck, [email], function (err, results) {
    if (err) {
      return res.status(500).json({ message: 'Error checking email', err });
    }
    if (results.length > 0) {
      return res.status(409).json({ message: 'Email already registered' });
    }

    // Hash the password before storing
    bcrypt.hash(password, 10, function (hashErr, hashedPassword) {
      if (hashErr) {
        return res.status(500).json({ message: 'Error hashing password', hashErr });
      }

      const sqlInsert = 'INSERT INTO users (name, email, password) VALUES (?, ?, ?)';
      db.query(sqlInsert, [name, email, hashedPassword], function (insertErr, result) {
        if (insertErr) {
          return res.status(500).json({ message: 'Error creating user', insertErr });
        }

        const token = jwt.sign(
          { id: result.insertId, email, role: 'user' },
          'secretKey',
          { expiresIn: '1d' }
        );

        return res.status(201).json({
          message: 'User registered successfully',
          token
        });
      });
    });
  });
});
```

## Login Route

This is the exact pattern from the course example:

```javascript theme={null}
// POST /api/login
app.post('/api/login', async function (request, response) {
  const { email, password } = request.body;

  // Check if user exists
  const sqlCheck = 'SELECT * FROM users WHERE email = ?';
  db.query(sqlCheck, [email], async function (err, results) {
    if (err) {
      return response.status(500).json({ message: 'Error checking user', err });
    }
    if (results.length === 0) {
      return response.status(404).json({ message: 'User is not registered' });
    }

    // Compare provided password with hashed password in DB
    const isMatch = await bcrypt.compare(password, results[0].password);
    if (!isMatch) {
      return response.status(401).json({ message: 'Invalid credentials' });
    }

    // Sign the token
    const token = await jwt.sign(
      {
        id: results[0].id,
        email: results[0].email,
        role: results[0].role
      },
      'secretKey',
      { expiresIn: '1d' }
    );

    if (!token) {
      return response.status(500).json({ message: 'No token provided, please try again' });
    }

    return response.status(200).json({ message: 'Login successful', token });
  });
});
```

<Note>
  In production, store your JWT secret in `.env` as `process.env.JWT_SECRET` instead of the hardcoded string `'secretKey'`. A hardcoded secret is fine for learning but must never be used in a deployed application.
</Note>

## verifyToken Middleware

```javascript theme={null}
// middleware/verifyToken.js
const jwt = require('jsonwebtoken');

function verifyToken(req, res, next) {
  const authHeader = req.headers.authorization;

  // Check header exists and starts with "Bearer "
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ message: 'No token provided' });
  }

  const token = authHeader.split(' ')[1];

  jwt.verify(token, 'secretKey', function (err, decoded) {
    if (err) {
      if (err.name === 'TokenExpiredError') {
        return res.status(401).json({ message: 'Token has expired' });
      }
      return res.status(401).json({ message: 'Invalid token' });
    }

    req.user = decoded; // { id, email, role, iat, exp }
    next();
  });
}

module.exports = verifyToken;
```

Using it on a protected route:

```javascript theme={null}
const verifyToken = require('./middleware/verifyToken');

// GET /api/profile — protected route
app.get('/api/profile', verifyToken, function (req, res) {
  const sql = 'SELECT id, name, email, role FROM users WHERE id = ?';

  db.query(sql, [req.user.id], function (err, results) {
    if (err) {
      return res.status(500).json({ message: 'Error fetching profile', err });
    }
    if (results.length === 0) {
      return res.status(404).json({ message: 'User not found' });
    }
    return res.status(200).json({ data: results[0] });
  });
});
```

## How the Auth Flow Works

<Steps>
  <Step title="Client registers">
    POST /api/auth/register. Server checks duplicate, hashes password, INSERTs row, returns JWT.
  </Step>

  <Step title="Client logs in">
    POST /api/login with email + password. Server SELECTs user, compares hash with bcrypt.compare(), signs and returns JWT.
  </Step>

  <Step title="Client stores token">
    Client stores the JWT (localStorage for learning; httpOnly cookie for production).
  </Step>

  <Step title="Client sends token">
    Every request to a protected route includes `Authorization: Bearer TOKEN` header.
  </Step>

  <Step title="verifyToken runs">
    Middleware extracts token, calls jwt.verify(), attaches decoded payload to req.user, calls next().
  </Step>

  <Step title="Handler uses req.user">
    Access req.user.id, req.user.email, req.user.role for personalized responses.
  </Step>
</Steps>

## JWT Claims in Payload

| Claim   | Set by      | Usage                                              |
| ------- | ----------- | -------------------------------------------------- |
| `id`    | jwt.sign    | User's MySQL row ID, used in db.query WHERE id = ? |
| `email` | jwt.sign    | User's email for display                           |
| `role`  | jwt.sign    | User's role for authorization checks               |
| `iat`   | JWT library | Issued-at timestamp (automatic)                    |
| `exp`   | JWT library | Expiry timestamp from expiresIn (automatic)        |

## Key Terms

| Term                                    | Definition                                                   |
| --------------------------------------- | ------------------------------------------------------------ |
| **JWT**                                 | JSON Web Token. Compact token for stateless authentication.  |
| **jwt.sign(payload, secret, options)**  | Creates and signs a new JWT.                                 |
| **jwt.verify(token, secret, callback)** | Validates a JWT and decodes its payload.                     |
| **Bearer token**                        | Auth scheme: client sends `Authorization: Bearer TOKEN`.     |
| **bcrypt.hash(password, rounds, cb)**   | Hashes a plaintext password asynchronously.                  |
| **bcrypt.compare(plain, hash)**         | Compares plaintext against stored hash. Returns true/false.  |
| **Token expiry**                        | Time after which the token is invalid. Set with `expiresIn`. |

## Common Mistakes

<Accordion title="Using 'secretKey' in production">
  The hardcoded string 'secretKey' is the same for every application that copies the example. In production, always use a long random string from process.env.JWT\_SECRET.
</Accordion>

<Accordion title="Same error for wrong email and wrong password">
  Return the same message ('Invalid credentials') for both cases. Different messages let attackers enumerate which emails are registered.
</Accordion>

<Accordion title="Not calling bcrypt.compare() before signing the token">
  Always verify the password matches before signing a token. Never sign a token just because the user was found.
</Accordion>

<Accordion title="Returning password hash in the response">
  Use SELECT only for the columns you need. Never SELECT \* and return the whole results object — it includes the hashed password.
</Accordion>
