Your API Is Public by Default — Let’s Fix That
Source: Dev.to
Here’s a scary thought: every API you deploy is public unless you actively make it private. Most backend breaches don’t come from elite hackers. They come from bored scripts, leaked tokens, or endpoints you forgot existed. Let’s walk through the most common API security mistakes I still see in production—and how to harden your backend without turning it into a usability nightmare.
Authentication vs. Authorization
Authentication answers who you are.
Authorization answers what you’re allowed to do.
Many APIs stop at auth.
GET /api/users/123
Authorization: Bearer <token>
If the token is valid, the request succeeds — even if the user shouldn’t see that data.
// ❌ Only checks auth
if (!req.user) throw new UnauthorizedError();
// ✅ Checks authorization
if (req.user.id !== params.userId && !req.user.isAdmin) {
throw new ForbiddenError();
}
Security rule of thumb: every read, write, and delete must answer “why is this user allowed?”
JWT Pitfalls
JWTs are great, but misused JWTs are dangerous.
Common issues
- No expiration (
exp) - Tokens stored in
localStorage - Tokens accepted forever after user logout
- No audience (
aud) or issuer (iss) validation
const payload = jwt.verify(token, JWT_SECRET, {
audience: 'api.myapp.com',
issuer: 'auth.myapp.com',
});
Best practices
- Short‑lived access tokens (5–15 min)
- Refresh tokens stored
httpOnly - Token rotation on refresh
JWTs don’t make you secure — your validation logic does.
Rate Limiting
If your API has login, OTP, password reset, search, public endpoints… it needs rate limiting. Period.
Why it matters
- Brute‑force attacks become trivial without limits
- Scrapers can eat your bandwidth
- One bad client can DOS your system
import rateLimit from 'express-rate-limit';
export const limiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 100, // requests per minute
});
Apply different limits for:
- Auth endpoints
- Public APIs
- Internal services
Security isn’t about blocking users — it’s about controlling abuse.
Minimal Data Exposure
A common real‑world breach:
{
"id": 42,
"email": "user@example.com",
"passwordHash": "...",
"isAdmin": false,
"createdAt": "..."
}
Nobody meant to expose passwordHash.
Safe DTO
// ✅ Safe DTO
return {
id: user.id,
email: user.email,
createdAt: user.createdAt,
};
Never rely on:
- ORM default serialization (
res.json(entity)) - “We’ll filter it on the frontend”
If you didn’t whitelist a field, it shouldn’t leave the server.
CORS Misconceptions
“It’s safe, our CORS is locked down.”
CORS only affects browsers. It does not stop:
- Server‑to‑server calls
- Bots, mobile apps, Postman,
curl
curl https://api.yoursite.com/secret
CORS is for preventing malicious websites from abusing your users’ browsers, not for protecting your API.
Secret Management
If your repo ever contained:
.envfiles- API keys
- Firebase configs
- AWS credentials
…assume they’re compromised.
Recommendations
- Store secrets only in environment variables
- Rotate keys regularly
- Never log secrets (even in debug)
- Scope keys to the minimum permissions
If a key leaks, the blast radius should be tiny, not existential.
Auditing & Logging
When something goes wrong, you need answers:
- Who did this?
- When?
- From where?
- Using which token?
If you don’t log security‑relevant actions, you’re blind.
Log (but don’t over‑log)
- Auth attempts
- Permission failures
- Admin actions
- Token refreshes
- Role changes
Be intentional, not verbose.
Microservices & Zero Trust
Just because an endpoint is:
- Behind a VPC
- On a private subnet
- “Only called by services”
…doesn’t mean it’s safe.
Harden service‑to‑service communication
- Service‑to‑service auth (e.g., mTLS, signed JWTs)
- Short‑lived tokens
- Network‑level rules
- Explicit permissions
Zero trust isn’t paranoia — it’s realism.
Summary: A Checklist of Boring, Disciplined Decisions
- Explicit authorization for every operation
- Minimal data exposure (whitelist fields)
- Rate limits everywhere (auth, public, internal)
- Short‑lived credentials (access & refresh tokens)
- Clear audit logs (auth, permission, admin actions)
Most breaches don’t come from sophisticated exploits. They stem from defaults you forgot to change. Implement these disciplined practices, and you’ll dramatically reduce your API’s attack surface.