Lock Down Your Web App: Security Tips, API Style – Part 1 by Alan Norman

Yo, what’s up! Alan Norman here, dropping some web app security knowledge. I’m all about that tight security life at JFrog, and today we’re covering the basics to get your app locked down.

I’ll be rocking Node.js (Express) for these examples, so let’s dive right in!

1) User-Agent Check

First up, let’s block sketchy requests with 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’s been blocked for good.');
  }
  next();
});

Pro Tip: Keep error messages simple. Don’t give hackers a playbook—just hit 'em with a “Forbidden.”

2) CORS

Now, let’s tighten up those cross-origin requests with CORS.

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

Pro Tip: Never rock with '*' in production—it’s a security nightmare. Don’t let randoms slide in with Postman!

3) Rate Limiting

Time to put some caps on requests and keep those bots in check.

const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 10 * 60 * 1000, // 10 minutes
  max: 100,
  message: 'Chill! Too many requests from this IP. Try again later.',
});
app.use(limiter);

Pro Tip: Tune your rate limits based on your traffic, and be vague with that error message—don’t let the hackers figure out your moves!

4) CSP (Content Security Policy)

CSP’s like a firewall for your frontend. It blocks XSS attacks by deciding what scripts can run.

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

Need to let in third-party stuff? Easy:

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"],
    },
  })
);

Rolling with inline scripts? Lock ’em down 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}'`],
    },
  })
);

Then use it on the frontend:

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

Final Tip: Lock those API keys by IP! Don’t let them wander unsecured, or you’re asking for trouble.

That’s it for Part 1! You’ve just boosted your API’s security. Stay tuned for Part 2, where we’ll tackle Brute Force and CAPTCHA!