Introduction
If you're building an API with Express.js, one of your top priorities should be security. Whether you're creating a small side project or a production-ready system, protecting your API endpoints from unauthorized access and attacks is critical.
In this guide, we’ll cover how to secure your Express.js API using two essential tools:
- JWT (JSON Web Tokens) for authentication
- Helmet.js for securing HTTP headers
We’ll take things step-by-step — perfect for beginners who are just getting started with API security.
What You’ll Learn
By the end of this guide, you’ll know how to:
- Create a secure Express.js API
- Use JWT to authenticate and protect endpoints
- Add Helmet.js for enhanced security headers
- Understand real-world best practices for securing APIs
Step 1: Setting Up Your Project
Let’s start with a simple Express.js setup.
In your terminal, run:
mkdir secure-api && cd secure-api
npm init -y
npm install express jsonwebtoken helmet dotenv
Here’s what these packages do:
- express → web framework for creating APIs
- jsonwebtoken → generates and verifies tokens
- helmet → helps secure your app by setting HTTP headers
- dotenv → stores sensitive data like secret keys safely
Next, create a file called server.js in your project root.
Step 2: Basic Express Server
Let’s get our Express app running.
const express = require('express');
const helmet = require('helmet');
const app = express();
// Middleware
app.use(express.json());
app.use(helmet());
// Simple route
app.get('/', (req, res) => {
res.json({ message: 'Welcome to Secure API ' });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Run your server:
node server.js
Now visit http://localhost:5000/ — you should see your welcome message!
Step 3: Understanding JWT (JSON Web Tokens)
A JWT (JSON Web Token) is a compact, secure way to transmit information between a client and server.
When a user logs in, the server issues a token. The client then includes that token with each request — allowing the server to verify who they are.
A JWT has three parts:
Header.Payload.Signature
- Header → contains algorithm and token type
- Payload → contains user data
- Signature → verifies token authenticity
Think of JWT as a secure entry pass — the server checks it before granting access to restricted routes.
Step 4: Implementing JWT Authentication
Let’s create two routes:
- Login route — issues a token.
- Protected route — accessible only with a valid token.
Update your server.js file:
require('dotenv').config();
const jwt = require('jsonwebtoken');
// Temporary user (in real apps, use a database)
const user = {
id: 1,
username: 'giftbalogun',
role: 'admin'
};
// Login route
app.post('/login', (req, res) => {
const { username } = req.body;
if (username !== user.username) {
return res.status(401).json({ message: 'Invalid username' });
}
const token = jwt.sign({ id: user.id, username: user.username }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ message: 'Login successful', token });
});
Now, add a middleware to protect routes:
function verifyToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.status(403).json({ message: 'Token required' });
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ message: 'Invalid or expired token' });
req.user = user;
next();
});
}
Finally, add a protected route:
app.get('/dashboard', verifyToken, (req, res) => {
res.json({ message: `Welcome ${req.user.username}!`, data: 'Secure dashboard data ' });
});
Test it:
- Login →
POST /login with { "username": "giftbalogun" }.
- Copy the token returned.
Visit → GET /dashboard and include your token in headers:
Authorization: Bearer your_token_here
You’ll now have secure access to the protected route!
Always store your JWT secret key in an environment file (.env). Never expose it in your code or version control.
Step 5: Securing HTTP Headers with Helmet.js
While JWT handles authentication, Helmet.js helps protect your app at a higher level.
Helmet adds several HTTP headers to defend against common web vulnerabilities like cross-site scripting (XSS), clickjacking, and content sniffing.
It’s already included in your middleware:
app.use(helmet());
You can also configure it for more control:
app.use(helmet({
contentSecurityPolicy: false, // Disable CSP if using inline scripts
crossOriginEmbedderPolicy: false,
}));
This step alone significantly increases your API’s security baseline.
Step 6: Combining JWT and Helmet for a Secure API
By now, your Express.js app:
- Issues and verifies JWTs for authentication.
- Uses Helmet to protect against HTTP-based attacks.
- Restricts access to routes with a verification middleware.
Together, these form a robust foundation for API security.
But here’s one last thing to remember:
Security is not a one-time setup — it’s an ongoing process.
Always update your dependencies, rotate secret keys, and monitor logs for suspicious activity.
Step 7: Testing and Debugging
You can test your API easily using Postman or cURL:
curl -X GET http://localhost:5000/dashboard \
-H "Authorization: Bearer your_token_here"
If everything works, you’ll see your secure response.
If not, check your console logs for:
- Expired tokens
- Missing headers
- Incorrect JWT secret
Debugging tip: Always log your token verification flow when testing authentication logic.
✅ Conclusion
Securing your Express.js API doesn’t have to be complicated.
With JWT, you can manage authentication securely.
With Helmet.js, you can harden your app against attacks.
Even if you’re just starting out, these two tools go a long way in building a professional-grade, production-ready backend.
Start simple, and layer your security as your app grows — that’s how real-world developers do it.
Final Tip
Don’t store JWTs in localStorage on the frontend — use HttpOnly cookies instead for better protection against XSS attacks.