DEV Community

Rigal Patel
Rigal Patel

Posted on

1

Top 5 Ways to Secure Your Express.js APIs

APIs are the backbone of modern web applications, and Express.js, being one of the most popular Node.js frameworks, makes building APIs simple and efficient. But simplicity doesn’t mean we can skip security. Every unsecured API endpoint is a potential vulnerability that attackers could exploit.

In this blog, I’ll share the top 5 ways to secure your Express.js APIs with actionable examples.

1. Enforce HTTPS and Secure Connections

Using HTTPS ensures that data exchanged between the client and server is encrypted, preventing attackers from intercepting sensitive information.

Here’s how to enforce HTTPS in an Express.js app:

const express = require('express');
const app = express();

app.use((req, res, next) => {
  if (req.headers['x-forwarded-proto'] !== 'https') {
    return res.redirect(`https://${req.headers.host}${req.url}`);
  }
  next();
});

app.get('/', (req, res) => {
  res.send('Hello, secure world!');
});

app.listen(3000, () => console.log('Server running on port 3000'));

Enter fullscreen mode Exit fullscreen mode

Pro Tip: Use tools like Let's Encrypt for free SSL certificates.

2. Use Helmet to Secure HTTP Headers

HTTP headers are often overlooked, but they can play a significant role in securing APIs. Helmet is a middleware that adds several security headers to your Express app.

const helmet = require('helmet');
const express = require('express');
const app = express();

app.use(helmet());

app.get('/', (req, res) => {
  res.send('Your API is now safer with Helmet!');
});

app.listen(3000, () => console.log('Server is protected by Helmet'));

Enter fullscreen mode Exit fullscreen mode

Helmet protects against:

  • Clickjacking (via X-Frame-Options)
  • XSS attacks (via Content-Security-Policy) And much more!

3. Protect Against Rate-Based Attacks

Prevent DDoS and brute-force attacks by limiting the number of requests a client can make. Use express-rate-limit to achieve this:

const rateLimit = require('express-rate-limit');
const express = require('express');
const app = express();

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests
  message: 'Too many requests, please try again later.'
});

app.use(limiter);

app.get('/', (req, res) => {
  res.send('Rate limiting is active!');
});

app.listen(3000, () => console.log('Server running with rate limiting'));

Enter fullscreen mode Exit fullscreen mode

4. Authenticate and Authorize Users

Authentication verifies a user’s identity, while authorization ensures they can only access allowed resources. Use JWT (JSON Web Tokens) for stateless authentication.

Example: Implementing JWT Authentication

const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();

const secret = 'your_jwt_secret';

// Login route to generate token
app.post('/login', (req, res) => {
  const user = { id: 1, username: 'john.doe' }; // Replace with actual user validation
  const token = jwt.sign(user, secret, { expiresIn: '1h' });
  res.json({ token });
});

// Middleware to verify token
const authenticateToken = (req, res, next) => {
  const token = req.header('Authorization')?.replace('Bearer ', '');
  if (!token) return res.status(401).send('Access Denied');

  try {
    const verified = jwt.verify(token, secret);
    req.user = verified;
    next();
  } catch (err) {
    res.status(400).send('Invalid Token');
  }
};

// Protected route
app.get('/protected', authenticateToken, (req, res) => {
  res.send('Welcome to the protected route!');
});

app.listen(3000, () => console.log('Server with JWT authentication running'));

Enter fullscreen mode Exit fullscreen mode

5. Validate and Sanitize User Input

SQL Injection and Cross-Site Scripting (XSS) attacks often target APIs with poorly validated input. Always validate and sanitize incoming data using libraries like Joi or express-validator.

Example: Input Validation with express-validator

const { body, validationResult } = require('express-validator');
const express = require('express');
const app = express();

app.use(express.json());

app.post(
  '/register',
  [
    body('email').isEmail().withMessage('Invalid email address'),
    body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters long')
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    res.send('Registration successful!');
  }
);

app.listen(3000, () => console.log('Input validation server running'));

Enter fullscreen mode Exit fullscreen mode

If you found this blog helpful, ❤️ give it a like and follow for more JavaScript tips and best practices!

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

nextjs tutorial video

Youtube Tutorial Series

So you built a Next.js app, but you need a clear view of the entire operation flow to be able to identify performance bottlenecks before you launch. But how do you get started? Get the essentials on tracing for Next.js from @nikolovlazar in this video series 👀

Watch the Youtube series

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay