DEV Community

Alan Norman
Alan Norman

Posted on

Security Tips, API Edition: How to Lock Down Your Web App — Part 1 by Alan Norman.

Hey folks, Alan Norman here! I’m all about that security life at JFrog, and today I’m dropping some tips on web app security. We’ll focus on the basics to get you started and keep your app tight.

I’m using Node.js (Express) for the examples, so let's dive in!

1) User-Agent Validation

First up, spot shady requests by adding User-Agent validation.

const allowedUserAgents = ['Mozilla/5.0'];
app.use((req, res, next) => {
  const userAgent = req.get('User-Agent');
  const isAllowed = allowedUserAgents.some(allowedAgent => userAgent.includes(allowedAgent));
  if (!isAllowed) {
    return res.status(403).send('Forbidden: Your device was blocked permanently for making requests.');
  }
  next();
});
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Don’t give away too much in your error messages—keep hackers guessing. A simple "Forbidden" keeps them in the dark.

2) CORS

Next, we’ve got CORS. It’s good for stopping unwanted cross-origin requests, but don’t expect miracles.

const corsOptions = {
  origin: ['https://your.app'],
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true,
};
app.use(cors(corsOptions));
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Never use '*' in production! It’s way too risky, and anyone with Postman can break through.

3) Rate Limiting

Let’s slap some limits on requests to block out those pesky bots.

const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 10 * 60 * 1000,
  max: 100,
  message: 'Too many requests from this IP, please try again later.',
});
app.use(limiter);
Enter fullscreen mode Exit fullscreen mode

Pro Tip: Customize the rate limit based on your app’s traffic. Watch that error message though—don’t give the hackers a clue!

4) CSP

Why CSP? It’s your line of defense against XSS attacks. Decide what gets loaded and block the rest.

const helmet = require('helmet');
app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'"],
      styleSrc: ["'self'"],
      imgSrc: ["'self'"],
    },
  })
);
Enter fullscreen mode Exit fullscreen mode

Need third-party stuff? No problem:

app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "https://cdnjs.cloudflare.com"],
      styleSrc: ["'self'", "https://fonts.googleapis.com"],
      imgSrc: ["'self'", "https://images.example.com"],
    },
  })
);
Enter fullscreen mode Exit fullscreen mode

Wanna use inline scripts? Secure them with a nonce:

const crypto = require('crypto');
app.use((req, res, next) => {
  res.locals.nonce = crypto.randomBytes(16).toString('hex');
  next();
});

app.use(
  helmet.contentSecurityPolicy({
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", (req, res) => `'nonce-${res.locals.nonce}'`],
    },
  })
);
Enter fullscreen mode Exit fullscreen mode

On the frontend, use that nonce:

<script nonce="${nonce}" src="/path/to/your/bundle.js"></script>
Enter fullscreen mode Exit fullscreen mode

Last bit—lock down those API keys. Make sure they’re restricted by IP. Don’t let them roam free, or you’ll be swimming in security issues.

That’s it for Part 1! You’ve just leveled up your API security game. Next, we’ll talk Brute Force attacks and Captcha. Stay tuned!

Top comments (0)