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:
- Stateless APIs: No server-side session storage needed, improving scalability.
- Cross-domain / CORS: Tokens can be sent across origins without issues like cookies.
- Mobile and Single-page Apps: Easily stored in local storage and sent in headers.
- Microservices: Each service can independently verify the token without contacting a central auth server.
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:
- Client sends credentials (e.g., username/password) to a login endpoint.
- Server verifies credentials and generates a JWT with relevant claims.
- Server returns the JWT to the client.
- Client stores the token (localStorage, sessionStorage, memory).
- Client includes the token in the Authorization header for each API request.
- Server validates the token's signature and claims, then proceeds or denies access.
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
- Use HTTPS everywhere: Tokens are bearer tokens; if intercepted over HTTP, an attacker can impersonate the user. Always serve APIs over HTTPS.
- Keep the secret safe: Store secrets in environment variables, never in code repositories. Use strong secrets (at least 256 bits for HMAC). Rotate secrets periodically.
- Set short expiration times: Tokens should expire quickly (15-30 minutes for access tokens). Implement refresh tokens with longer lifetimes but stored securely (HTTP-only cookies).
- Validate claims: Check
iss(issuer),aud(audience) if needed,exp(expiration). Thejsonwebtokenlibrary'sverifymethod automatically checksexpandnbf. - Implement token revocation: JWTs are stateless, so you can't simply "log out" by deleting a server-side session. Use a token blacklist (e.g., Redis set of invalidated tokens) or short expiry with refresh token rotation.
- Use appropriate signing algorithms: Prefer RS256 (asymmetric) for distributed systems where multiple services need to verify tokens without sharing the private key. For simple monolithic APIs, HS256 is fine but keep secret secure.
- Store tokens securely on client: Avoid localStorage for sensitive tokens due to XSS risks. Use HTTP-only cookies with Secure and SameSite flags for web apps, or secure mobile storage. If localStorage is used, mitigate XSS.
- Include only necessary claims: Keep payload small to reduce overhead and avoid leaking sensitive data. Never include passwords or secrets in the token payload.
- Protect against CSRF: If using cookies for tokens, implement CSRF protection (double-submit cookie pattern, etc.). Bearer tokens in headers are not vulnerable to CSRF.
- Log and monitor: Track token usage patterns, detect anomalies like many requests from one token in short time, or token reuse from different IPs.
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.