Skip to content

10 Authentication Jwt

DodaTech 4 min read

title: JWT Authentication in Node.js REST APIs weight: 20 date: 2026-06-28 lastmod: 2026-06-28 description: Learn JWT authentication for Node.js REST APIs including token generation, verification, refresh tokens, blacklisting, and secure storage with Express and Koa. tags: [api-development, nodejs]


JWT (JSON Web Token) authentication for Node.js REST APIs generates signed tokens containing user identity and claims, enabling stateless authentication without server-side sessions and supporting expiration and refresh flows.

```mermaid
flowchart TD
  A[Client] -->|POST /auth/login| B[Server]
  B --> C{Valid credentials?}
  C -->|Yes| D[Generate JWT]
  D -->|Access + Refresh Tokens| A
  A -->|GET /api/users with Bearer token| E[Auth Middleware]
  E --> F{Valid token?}
  F -->|Yes| G[Route Handler]
  F -->|No| H[401 Unauthorized]
  style A fill:#e1f5fe
  style D fill:#c8e6c9
  style E fill:#fff9c4
  style H fill:#ffcdd2

JWT tokens have three parts: header (algorithm), payload (user data, claims, expiration), and signature (verified with secret). Access tokens are short-lived (15-60 minutes). Refresh tokens are long-lived (days) and used to get new access tokens without re-authentication.

Think of JWT like a theme park wristband. The wristband (token) shows who you are (payload), when it expires (exp), and has a hologram (signature) that guards can verify. If your wristband expires, you go to guest services (refresh token endpoint) to get a new one.

Example: Login Endpoint

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

app.post('/api/auth/login', async (req, res, next) => {
  try {
    const { email, password } = req.body;

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

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

    // Generate tokens
    const accessToken = jwt.sign(
      { userId: user.id, role: user.role },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    const refreshToken = jwt.sign(
      { userId: user.id, tokenVersion: user.tokenVersion },
      process.env.JWT_REFRESH_SECRET,
      { expiresIn: '7d' }
    );

    res.json({
      accessToken,
      refreshToken,
      expiresIn: 900
    });
  } catch (error) {
    next(error);
  }
});

Example: Auth Middleware

const authenticate = (req, res, next) => {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }

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

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
    }
    return res.status(401).json({ error: 'Invalid token' });
  }
};

// Protect routes
app.get('/api/users/me', authenticate, (req, res) => {
  res.json({ user: req.user });
});

Example: Token Refresh

app.post('/api/auth/refresh', async (req, res, next) => {
  try {
    const { refreshToken } = req.body;

    if (!refreshToken) {
      return res.status(400).json({ error: 'Refresh token required' });
    }

    // Verify refresh token
    const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);

    // Check if token was revoked (user logout or password change)
    const user = await findUser(decoded.userId);
    if (!user || decoded.tokenVersion !== user.tokenVersion) {
      return res.status(401).json({ error: 'Token revoked' });
    }

    // Issue new access token
    const newAccessToken = jwt.sign(
      { userId: user.id, role: user.role },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken, expiresIn: 900 });
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Refresh token expired. Login required.' });
    }
    next(error);
  }
});

Common Mistakes

  1. Storing JWT secret in code — Never hard-code JWT secrets. Use environment variables. Rotate secrets periodically.
  2. Including sensitive data in payload — JWT payloads are base64-encoded, not encrypted. Do not include passwords or credit card numbers.
  3. Using long-lived access tokens — Access tokens should expire in minutes, not hours or days. Use refresh tokens for long-lived sessions.
  4. Not validating token signature — Always verify the signature with jwt.verify(). Accepting unsigned tokens allows anyone to forge identities.
  5. Storing tokens in localStorage without protection — localStorage is accessible to JavaScript (XSS). Use httpOnly cookies for browser apps when possible.

Practice Questions

  1. What are the three parts of a JWT token?
  2. Why should access tokens have short expiration times?
  3. What is the purpose of a refresh token?
  4. How do you revoke JWT tokens?
  5. Challenge: Build a complete JWT authentication system with login, register, token refresh, and logout (token blacklisting). Include password hashing with bcrypt and token version tracking for revocation.

FAQ

What is the difference between JWT and session-based auth?

JWT is stateless: no server storage needed. Session auth stores sessions on the server. JWT scales better but cannot be revoked without additional infrastructure.

How do I handle token revocation?

Use a token version number stored in the database. Increment it on logout or password change. Check the version during authentication.

Should I store JWT in localStorage or cookies?

httpOnly cookies are more secure against XSS. localStorage is simpler for mobile apps. Both have tradeoffs. Consider your threat model.

What algorithm should I use for JWT signing?

Use HS256 (HMAC with SHA-256) for single-server apps. Use RS256 (RSA with SHA-256) for distributed systems where multiple services need to verify tokens.

How do I handle token rotation?

Issue a new refresh token with each refresh. Invalidate the old refresh token. This limits the window for stolen refresh tokens.

Mini Project

Build a complete JWT authentication system for an Express API. Implement register (with bcrypt password hashing), login (returns access + refresh tokens), auth middleware, token refresh endpoint, logout (increments token version), and a protected user profile endpoint.

What's Next

Now learn about authorization middleware in Building REST APIs with Node.js.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro