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 403Try 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.
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.
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.
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
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.
AI tutor
The optional AI tutor isn't enabled in this deployment. All lessons, exercises, quizzes, and search work fully without it.