← Back to DevBytes

How to Secure an API with JWT Authentication

What is JWT Authentication?

JWT (JSON Web Token) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with HMAC algorithm) or a public/private key pair using RSA or ECDSA.

In the context of API authentication, JWTs are used to grant access to resources without relying on server-side session state. After a user logs in, the server generates a JWT containing claims about the user (like user ID, role) and signs it. The client then includes this token in subsequent requests, typically in the Authorization header as a Bearer token. The server validates the token and extracts the user identity to authorize the request.

Why JWT Matters for API Security

🚀 Deploy your AI agent in 10 minutes

Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.

Try it free →

Traditional session-based authentication stores session data on the server, which requires a shared session store in distributed systems. JWT eliminates this need by making the token self-contained. This makes it ideal for:

However, JWT also introduces security considerations: tokens must be protected from theft (XSS, etc.) and should have short lifetimes with refresh mechanisms.

How JWT Authentication Works

A typical JWT authentication flow involves:

Practical Implementation: Securing a Node.js/Express API

Let's build a simple API with JWT authentication using Node.js and Express. We'll use the jsonwebtoken library for token handling and bcrypt for password hashing.

1. Setup and Dependencies

Initialize a Node project and install packages:

npm init -y
npm install express jsonwebtoken bcrypt body-parser

2. User Model and Login Endpoint

For demonstration, we'll simulate a user database. In production, connect to a real database.

// server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const bodyParser = require('body-parser');

const app = express();
app.use(bodyParser.json());

const SECRET_KEY = 'your-secret-key-keep-it-safe'; // Use env vars in production

// Mock user database (in-memory for demo)
const users = [
  {
    id: 1,
    username: 'alice',
    // password: 'password123' hashed
    passwordHash: '$2b$10$...' // bcrypt hash of 'password123'
  }
];

// Login route
app.post('/login', async (req, res) => {
  const { username, password } = req.body;
  const user = users.find(u => u.username === username);
  if (!user) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }
  const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
  if (!isPasswordValid) {
    return res.status(401).json({ message: 'Invalid credentials' });
  }
  // Create token
  const payload = {
    sub: user.id,       // subject (user ID)
    username: user.username,
    role: 'user',       // optional role claim
    iat: Math.floor(Date.now() / 1000)
  };
  const token = jwt.sign(payload, SECRET_KEY, { expiresIn: '1h' });
  res.json({ token });
});

3. JWT Verification Middleware

Create a middleware that checks the Authorization header, verifies the token, and attaches user info to the request.

// middleware/auth.js
const jwt = require('jsonwebtoken');
const SECRET_KEY = process.env.JWT_SECRET || 'your-secret-key-keep-it-safe';

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

  if (!token) {
    return res.status(401).json({ message: 'Access token missing' });
  }

  jwt.verify(token, SECRET_KEY, (err, decoded) => {
    if (err) {
      // Token expired or invalid signature
      return res.status(403).json({ message: 'Token invalid or expired' });
    }
    req.user = decoded; // attach user info to request
    next();
  });
}

module.exports = authenticateToken;

4. Protecting API Routes

Now apply the middleware to any route that requires authentication.

// Protected route - get user profile
app.get('/profile', authenticateToken, (req, res) => {
  // req.user contains the decoded token payload
  res.json({
    message: `Welcome ${req.user.username}!`,
    user: {
      id: req.user.sub,
      username: req.user.username,
      role: req.user.role
    }
  });
});

// Admin-only route using role claim
function authorizeRole(role) {
  return (req, res, next) => {
    if (req.user.role !== role) {
      return res.status(403).json({ message: 'Insufficient permissions' });
    }
    next();
  };
}

app.get('/admin', authenticateToken, authorizeRole('admin'), (req, res) => {
  res.json({ message: 'Admin panel' });
});

// Start server
app.listen(3000, () => console.log('API running on port 3000'));

5. Client-Side Usage Example (JavaScript fetch)

On the frontend, store the token after login and attach it to requests.

// Login function
async function login(username, password) {
  const response = await fetch('/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });
  const data = await response.json();
  if (data.token) {
    localStorage.setItem('jwt', data.token); // store token
  }
  return data;
}

// Authenticated request
async function fetchProfile() {
  const token = localStorage.getItem('jwt');
  const response = await fetch('/profile', {
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });
  const data = await response.json();
  console.log(data);
}

Best Practices for JWT API Security

Conclusion

JWT authentication provides a robust, scalable method for securing APIs, especially in stateless and distributed architectures. By understanding its mechanics and implementing proper verification middleware, you can effectively protect your endpoints. However, security is not a one-time setup; adhere to best practices like using HTTPS, short-lived tokens, secure storage, and token revocation strategies. With careful implementation, JWT-based authentication can be a cornerstone of modern API security.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles