Lock Down Your Web App: API Security Tips, Part 2 – By Alan Norman
Yo, what’s good! Let’s do a quick throwback to Part 1 where we covered the basics: User-Agent checks, CORS, Rate Limiting, and CSP. Now, we’re about to dive headfirst into some real user authentication.
1. Startin' Off with CAPTCHA
First up, CAPTCHA. It’s simple but packs a punch. Companies either roll with Google reCAPTCHA v2, v3, Cloudflare, or some DIY setup.
We’re gonna focus on Google reCAPTCHA v3. What’s the vibe? Unlike the older versions, v3 won’t hit users with those annoying puzzles. It runs smooth in the background, checking out each visitor’s behavior to figure out if they’re human or some sketchy bot.
How to Set Up reCAPTCHA v3
- Frontend: Use whatever fits your tech stack. Since I’m team React, I ride with
@react-google-recaptcha
. First things first, grab your Site Key and Secret Key by setting up reCAPTCHA here.
No need to break down the frontend details; there's more than one way to skin that cat.
- Backend: Here's the magic:
app.post('/api/form', async (req, res) => {
const { token } = req.body;
try {
const response = await axios.post(`https://www.google.com/recaptcha/api/siteverify?secret=${RECAPTCHA_SECRET_KEY}&response=${token}`);
const { success, score } = response.data;
if (success && score > 0.5) {
// Token is good, score looks legit
res.json({ message: 'Success' });
} else {
// Something shady, block 'em
res.status(400).json({ message: 'Failed reCAPTCHA' });
}
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Server error' });
}
});
Pro Tip: Keep your error messages vague. No need to hand out clues—hackers can stay guessing.
2. Blockin' Brute Force
Next up, you gotta watch out for repeated login fails. If someone’s throwing out fail after fail, they’re probably trying to brute-force their way in. Here’s how to shut that down:
- Redis Setup: Redis is your go-to for keeping track of failed logins. It’s quick and solid.
Track Login Attempts:
Controller Example:
const Login = async (req, res) => {
const { email, password } = req.body;
try {
const token = await LoginModule.login(email, password); // Log in attempt
// Do your thing here
} catch (e) {
// Login failed, time to track it
RedisService.setUserAttempts(`attempts_${email}`, /* increment and set TTL */);
}
}
So, you’ve got a LoginModule
that does the login hustle and gives you a token if it’s all good. Meanwhile, RedisService
is counting every failed login attempt.
Middleware Example:
const USER_ATTEMPTS = 3; // Tweak based on your setup
const UserLoginAttempts = (req, res, next) => {
const { email } = req.body;
const userAttempts = RedisService.getUserAttempts(`attempts_${email}`);
if (userAttempts >= USER_ATTEMPTS) {
return res.status(403).send('Too many attempts. Chill out for 2 minutes.');
}
next();
}
Pro Tip: Keep your Redis setup running fast and clean. It’s gotta be reliable, especially when handling login attempts.
Alright, that’s a wrap for this round. You’ve got CAPTCHA and brute force blocks locked down, so your app’s security game is stepping up.
Stay tuned for the next chapter where we’re gonna break down Tokens and Cookies!