intermediate18 min

API Security Basics for Testers

Concrete, non-specialist security checks every API tester should reflexively perform, without needing to be a penetration tester.

What you'll learn

  • Identify a broken object-level authorization defect in a simulated API
  • Design an input-sanitization check for an injection-style attempt
  • Explain why security testing is a shared responsibility, not only a specialist's job

Prerequisites

Explanation

This lesson isn't a substitute for a dedicated security review — it's the baseline every API tester can and should check, the same way every tester checks basic accessibility without being an accessibility specialist.

The single most common real-world API vulnerability, consistently topping industry vulnerability lists, has a plain-language description: an authenticated user can access or modify data belonging to someone else, just by changing an id in the request. A user logged in as account 42 requests GET /orders/42 — fine, that's their own data. If they then request GET /orders/43 (someone else's order) and the server happily returns it because it only checked "is this user logged in," not "does this user own order 43," that's a broken authorization check, not a broken authentication check — the user was correctly identified, just incorrectly permitted. Testing for this is refreshingly direct: as user A, deliberately request user B's resource by id, and confirm you get a 403 or 404, never user B's real data.

Input sanitization is the other baseline check: does the API safely handle input containing characters or patterns that could be interpreted as code rather than data? A search field that receives a value containing SQL-like syntax (' OR '1'='1) should be treated as a literal search string, never executed as part of a database query. A field that receives HTML/script tags should never have that content echoed back and executed by a browser reading the response. A tester doesn't need to construct a real working exploit to test this — sending the suspicious-looking input and confirming it's treated as inert data, not executed or reflected unsafely, is a meaningful, non-specialist check.

Security testing is not exclusively a specialist's job for the same reason accessibility testing isn't: waiting for a dedicated security review at the very end of a project finds these issues far later and far more expensively than a tester who reflexively checks "can I access someone else's data by changing an id" on every new endpoint they touch.

Example

A simulated authorization check for broken object-level authorization: does the API verify ownership, not just identity?

const orders = { 42: { owner: "userA", total: 100 }, 43: { owner: "userB", total: 250 } };

// Buggy: only checks that SOMEONE is logged in, not that they own the resource.
function getOrderBuggy(orderId, requestingUser) {
  return orders[orderId] ? { status: 200, body: orders[orderId] } : { status: 404 };
}

// Fixed: checks ownership too.
function getOrderFixed(orderId, requestingUser) {
  const order = orders[orderId];
  if (!order) return { status: 404 };
  if (order.owner !== requestingUser) return { status: 403 };
  return { status: 200, body: order };
}

console.log(getOrderBuggy(43, "userA"));  // leaks userB's order to userA!
console.log(getOrderFixed(43, "userA"));  // correctly 403

Try it yourself

Try getOrderFixed with the correct owner (userB requesting order 43) and confirm it succeeds.

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Guided exercise

Guided exercise

Using getOrderFixed already defined, verify it correctly blocks cross-user access by setting resultStatus to the status code returned when userA (not the owner) requests order 43 (owned by userB).

Checks: correctly confirms cross-user access is blocked

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Independent exercise

Independent exercise

Write a function isSuspiciousInput(value) that returns true if the string value contains a SQL-injection-style pattern (a single quote followed by OR, case-insensitive, like "' OR '1'='1") or an HTML script tag ('<script'), false otherwise.

Checks: normal input is not flagged · SQL-injection-style input is flagged · a script tag is flagged

Code editor. Press Escape then Tab to leave the editor if keyboard focus becomes trapped. Press Control+Shift+M inside the editor to toggle Tab-key focus trapping.

Loading editor…

Stuck? Get a hint.

Common mistakes

  • Testing authorization only with the resource owner's own data, never deliberately requesting another user's resource by id.
  • Assuming input sanitization is entirely the framework's job and never explicitly testing it with suspicious-looking input.
  • Waiting for a dedicated security review at the end of a project instead of checking basic authorization and input handling on every new endpoint as it's built.

Knowledge check

Knowledge check

1. A logged-in user can view another user's private order just by changing the order id in the URL. What kind of defect is this?
2. Why might a tester send input like an embedded script tag or a SQL-like string to a search field?
3. Why is checking for broken object-level authorization something every API tester should do, not just security specialists?

Takeaway

Broken object-level authorization (accessing another user's data by changing an id) and unsafe handling of suspicious input are baseline, non-specialist checks every API tester can and should perform on every endpoint.

Summary

This lesson covered testing for broken object-level authorization by deliberately requesting another user's resource, and checking that suspicious input is safely treated as inert data.

References

Your notes

Notes save automatically.

Finished this lesson?

Mark it complete to track your progress and schedule a future review.