
When it comes to API security, most minds jump straight to authentication and authorisation at the endpoint. And while crucial, these are just the tip of the iceberg. This post dives into the deeper layers of API security, revealing how to protect your data from even authorised users.
The Common Assumption: Endpoint-Level Security
The standard approach to API security usually covers two things: Authentication and Authorisation. This focus often stems from their straightforward implementation and immediate impact on protecting routes. For most cases, securing an API seems to stop here…
If a user is authenticated and has access to /items/:id, that’s considered good enough. Right?
The Deeper Problem: Data-Level Authorisation
Let’s say your system has an API to fetch item details:
GET /items/:
A user is authenticated, and they’re authorised to access this endpoint. But the question is — should they have access to that specific item?
Imagine the item has a companyId attribute. The authenticated user also belongs to a company. In that case, users should only be able to access items where item.companyId === user.companyId.
This is where resource-level authorisation becomes essential. It’s not just about whether a user can call an endpoint — it’s about whether they can access the data behind it.
Going Even Deeper: Field-Level Access Control
Let’s say the user is authorised to access the item — does that mean they should see everything in it?
Not necessarily. Consider role-based visibility:
- A Manager role might be allowed to view salaries, internal notes, or audit logs.
- A Viewer role might only see public-facing data.
This is known as field-level security. It means the API should return different views of the same data based on the user’s role or context. Implementing a view layer to sanitise and filter the response is crucial.
Real-World Pitfalls of Ignoring This
- Over-fetching sensitive data: APIs might return fields users shouldn’t see because no filtering was applied e.g. exposing an employee’s salary to a general user.
- Broken Access Control (BOLA/IDOR vulnerabilities): Without resource-level checks, a user authorised to access their own data might easily manipulate IDs to access another organisation’s or user’s sensitive information. This is a common and critical vulnerability.
- Relying on frontend filters: This is not security. Never trust the client to enforce access rules.
Practical Code Pattern (Node.js Example)
Here’s a simple example of how to implement resource- and field-level control using Node.js with Express:
// req.user.companyId and req.user.role would typically come
from // a prior authentication/authorization middleware (e.g., JWT decoding) // Resource-level check
const item = await db. getItem (req. params .); // Fetch the item
from the database (item. companyId !== req. user . companyId ) {
return res. status ( 403 ). json ({
error : "Access denied" }); } // Field-level filtering based
on role
const filterFieldsByRole = ( item, role ) => {
(role === 'manager' )
return item; (role === 'viewer' )
return {
: item., name : item. name , description : item. description };
return {}; };
const filteredItem = filterFieldsByRole (item, req. user . role );
res. json (filteredItem);
from // a prior authentication/authorization middleware (e.g., JWT decoding) // Resource-level check
const item = await db. getItem (req. params .); // Fetch the item
from the database (item. companyId !== req. user . companyId ) {
return res. status ( 403 ). json ({
error : "Access denied" }); } // Field-level filtering based
on role
const filterFieldsByRole = ( item, role ) => {
(role === 'manager' )
return item; (role === 'viewer' )
return {
: item., name : item. name , description : item. description };
return {}; };
const filteredItem = filterFieldsByRole (item, req. user . role );
res. json (filteredItem);
Note: req.user typically holds user details extracted from an authentication token or session after a successful authentication step.
Anti-Patterns to Avoid
❌ Common Mistakes
- Trusting frontend for access filtering
- Returning raw database records (
SELECT *) without field filtering - Doing access checks after fetching sensitive data from the database: This is inefficient and still exposes sensitive data to your application layer longer than necessary. Ideally, filter at the query level or before the data is fully materialised.
- Exposing internal sequential or predictable object IDs/references (e.g.,
userId = 1,orderId = 123): Use UUIDs or obfuscated IDs to prevent enumeration attacks, where an attacker guesses valid IDs.
API Security Checklist (Server-Side)
✅ Enforce Authentication for all sensitive endpoints (no unauthenticated access to private APIs). ✅ Role-Based or Policy-Based Authorisation implemented at: — Endpoint level — Resource level (data ownership checks) — Field level (role-based visibility) ✅ Use the Principle of Least Privilege — users should only see what they need. ✅ Apply Secure Defaults — deny access by default when unsure. ✅ Never Trust the Client for access enforcement. ✅ Validate Input Strictly — prevent injection attacks (SQL/NoSQL/Command). ✅ Sanitise Responses — remove sensitive fields before sending data. ✅ Paginate Large Responses — to avoid accidental data leaks. ✅ Log and Monitor failed or suspicious access attempts. ✅ Don’t Leak Info in Errors — return generic messages for unauthorised access. ✅ Use HTTPS/TLS Everywhere — no plain HTTP for APIs. ✅ Rotate and Secure Secrets (API keys, JWT signing keys) regularly.Encourage Secure Defaults
A key principle of secure design:
If a user’s permissions are unclear — deny access by default. If a field is not explicitly allowed for a role — don’t return it.Secure defaults protect against future gaps in role mapping or logic.
Final Thoughts
Securing APIs is not just about who can hit what endpoint. Real security drills down to what data is accessible, by whom, and to what extent. If your system handles sensitive data or supports multiple roles and organisations, make sure your API security model supports those real-world complexities.
Because at the end of the day, an endpoint might be secure — but the data behind it can still leak if you’re not careful. Prioritise data-level security in your API design — your users (and their data) will thank you.
Note: If you’ve ever been surprised by a seemingly safe API returning more than expected, this blog was written for you. Let’s build secure systems, not just secure routes.



