Back to blog
#API#Security#OWASP#BOLA#GraphQL#REST#Tutorial

Securing Your REST/GraphQL APIs — Essential Best Practices

LLLEBONI BAKLA Lionel•📅 April 15, 2026• ⏱️ 11 min

For six months, I tested several vulnerable environments (CRAPI, VAPI, DVGA) and helped development teams secure their APIs. Here is a summary of the most frequent vulnerabilities — and above all how to fix them.

The API Risk Landscape

The OWASP API Security Top 10 remains the reference. The three flaws I encounter most often:

# Vulnerability Frequency
1 Broken Object Level Authorization (BOLA) 🔴 Very high
2 Injection (SQL, NoSQL, command) 🔴 High
3 Broken Authentication 🟡 Medium

1. BOLA — Broken Object Level Authorization

The problem

A user accesses /api/orders/123 — but nothing verifies that they actually own order 123. They just need to change 123 to 124, 125… to read other people’s data.

This is the API equivalent of web IDOR. It is the most common vulnerability in APIs.

Demo (CRAPI style)

GET /api/orders/1001 HTTP/1.1
Authorization: Bearer <token_user_A>

→ Response: order 1001 data (belonging to user_A). ✅

GET /api/orders/1002 HTTP/1.1
Authorization: Bearer <token_user_A>

→ Response: order 1002 data (belonging to user_B). ❌ BOLA!

The fix

Always verify ownership server-side — never trust the client.

// ❌ BAD
app.get('/api/orders/:id', auth, async (req, res) => {
  const order = await Order.findById(req.params.id);
  res.json(order);
});

// ✅ GOOD
app.get('/api/orders/:id', auth, async (req, res) => {
  const order = await Order.findOne({
    _id: req.params.id,
    userId: req.user.id, // ownership check
  });
  if (!order) return res.status(404).json({ error: 'Not found' });
  res.json(order);
});

2. Injections

SQL Injection

Always use parameterized queries:

// ❌ BAD — concatenation
const q = `SELECT * FROM users WHERE name = '${name}'`;

// ✅ GOOD — parameterized
const q = 'SELECT * FROM users WHERE name = $1';
db.query(q, [name]);

NoSQL Injection (MongoDB)

Registration: {"username": {"$ne": null}} can match all users!

// ❌ BAD
User.find({ username: req.body.username });

// ✅ GOOD — validate the type
if (typeof req.body.username !== 'string') {
  return res.status(400).json({ error: 'Invalid' });
}

GraphQL — Depth & Complexity

On GraphQL (DVGA demo), depth and complexity attacks can DoS the server:

# Recursive bomb
query { user { friends { user { friends { user { ... } } } } } }

Fix: limit query depth and cost.

import depthLimit from 'graphql-depth-limit';
import costAnalysis from 'graphql-cost-analysis';

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(5),
    costAnalysis({ maximumCost: 1000 }),
  ],
});

3. Weak Authentication

JWT — classic pitfalls

// ❌ NEVER DO THIS
const decoded = jwt.decode(token);
if (decoded.admin) { /* grant admin */ }

// ✅ ALWAYS VERIFY THE SIGNATURE
const decoded = jwt.verify(token, SECRET);

Mistakes I often see:

  • alg: none accepted
  • Weak shared secret
  • JWT without expiration
  • Non-rotating refresh token

Rate limiting

Without rate limiting, brute-force is guaranteed:

import rateLimit from 'express-rate-limit';

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5,
  message: 'Too many attempts',
});

app.post('/api/login', loginLimiter, loginHandler);

4. Mass Assignment

A user sends an unexpected field (role: 'admin') and your code does User.create(req.body).

// ❌ BAD
const user = await User.create(req.body);

// ✅ GOOD — whitelist
const { username, email, password } = req.body;
const user = await User.create({ username, email, password });

5. Misconfigured CORS

// ❌ DANGER
app.use(cors({ origin: '*' }));

// ✅ SAFE
app.use(cors({
  origin: ['https://app.mydomain.com'],
  credentials: true,
}));

End-of-development checklist

Before putting an API into production:

  • Authorization verified on every sensitive endpoint
  • Parameterized queries everywhere (zero SQL concatenation)
  • Input validation (zod, joi, class-validator)
  • Rate limiting on login, register, reset password
  • JWT with strong secret, short expiration, refresh rotation
  • HTTPS mandatory, HSTS enabled
  • Security logs (auth failures, denied access)
  • Restrictive CORS
  • Headers: CSP, X-Content-Type-Options, X-Frame-Options
  • Testing with tools (Burp, OWASP ZAP, nuclei)

Tools I recommend

Tool Usage
Burp Suite Proxy, scanner, intruder
OWASP ZAP Free scanner, CI/CD automation
nuclei CVE templates, mass detection
ffuf Endpoint and parameter fuzzing
graphql-playground GraphQL introspection and testing
jwt_tool JWT analysis and forging

Conclusion

90% of the API vulnerabilities I find come from a broken authorization model (BOLA) or missing input validation. Covering these two points properly eliminates most of the risk.

API security is not an “extra” topic — it is an axis to integrate from the design phase, and to test continuously via automated scans in your CI/CD pipelines.

“A secure API is not an API that does nothing. It is an API that refuses well.”

About the author

LEBONI BAKLA Lionel — Cybersecurity & Applied AI Engineer. Ethical Hacker • Bug Hunter • Junior DevSecOps • FullStack Developer.