APIs are now the front door of almost every product. Your mobile app, your React or Next.js front end, your partner integrations and, increasingly, AI agents all talk to the same endpoints. That makes your API the first place an attacker looks, and the place where a single missing check can leak every customer's data.
The good news is that the most common API weaknesses are well understood. The OWASP API Security Top 10 (the current edition is the 2023 list, still the reference in 2026) describes them clearly, and independent research such as the 42Crunch State of API Security 2026 report shows that real-world findings still cluster around the same top categories, especially authorization flaws. This guide walks through each risk in plain language, with concrete fixes for Laravel and Node.js APIs, so founders, CTOs and engineering leads can check where they stand.
Why API security looks different from classic web security
Traditional web apps render pages on the server and hide most data behind the UI. APIs do the opposite: they hand structured data straight to the client and trust the client to show only what it should. A few consequences follow:
- Object IDs are visible. Every request carries IDs like
/api/invoices/1042, so changing a number is the easiest attack there is. - The UI is not a security boundary. Hiding an admin button in React does nothing if the endpoint behind it accepts the call.
- Responses are often over-shared. Returning a whole database row "because the front end filters it" exposes fields nobody should see.
- Machines call APIs at machine speed. Bots, scrapers and AI agents can hit an endpoint thousands of times a minute, so missing limits turn into outages or surprise bills.
The OWASP API Security Top 10, explained with fixes
API1: Broken Object Level Authorization (BOLA)
BOLA happens when an endpoint accepts an object ID and acts on it without checking that the caller is allowed to touch that specific object. User A requests GET /api/orders/12345, which belongs to user B, and gets it back. It is the most reported API flaw for a simple reason: authentication is usually in place, but per-object ownership checks are forgotten on one or two endpoints.
Laravel fix: use policies and scope queries to the current user or tenant, so a foreign ID simply returns 404 or 403.
// app/Policies/OrderPolicy.php
public function view(User $user, Order $order): bool
{
return $order->team_id === $user->current_team_id;
}
// OrderController.php
public function show(Order $order)
{
$this->authorize('view', $order);
return new OrderResource($order);
}
// Even better: never load other tenants' rows in the first place
$order = $request->user()->currentTeam->orders()->findOrFail($id);
Node.js fix: always include the owner in the query, not just the ID.
// Express + Prisma
app.get('/api/orders/:id', requireAuth, async (req, res) => {
const order = await prisma.order.findFirst({
where: { id: req.params.id, teamId: req.user.teamId },
});
if (!order) return res.status(404).json({ error: 'Not found' });
res.json(toOrderDto(order));
});
UUIDs make IDs harder to guess, but they are not a substitute for the ownership check. Treat them as defence in depth.
API2: Broken Authentication
This covers weak login flows, tokens that never expire, missing brute-force protection, JWTs accepted without proper signature or algorithm checks, and password reset flows that can be abused.
- In Laravel, use first-party tools such as Sanctum (SPA and mobile tokens) or Passport (full OAuth2) rather than hand-rolled token tables. Give tokens abilities and expiry.
- In Node.js, validate JWTs with a maintained library, pin the expected algorithm, check
iss,audandexp, and keep access tokens short-lived with refresh-token rotation. - Rate limit login, OTP and password-reset endpoints much more tightly than the rest of the API.
- Offer MFA for admin and high-value accounts, and never return different errors for "user not found" versus "wrong password".
import jwt from 'jsonwebtoken';
const payload = jwt.verify(token, process.env.JWT_PUBLIC_KEY, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com',
audience: 'api.example.com',
});
API3: Broken Object Property Level Authorization (BOPLA)
This merges two older problems: excessive data exposure (returning fields the caller should not see) and mass assignment (letting the caller set fields they should not change, such as role, is_admin or balance).
In Laravel, return API Resources instead of raw models, and only fill validated data:
class UserResource extends JsonResource
{
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->when($request->user()?->can('viewEmail', $this->resource), $this->email),
];
}
}
// Controller: only validated, allowed keys reach the model
$user->update($request->validated());
In Node.js, define explicit response DTOs and validate request bodies with a schema library such as Zod, rejecting unknown keys:
import { z } from 'zod';
const UpdateProfile = z.object({
name: z.string().min(1).max(100),
timezone: z.string().max(64),
}).strict(); // unknown fields like "role" cause a validation error
const data = UpdateProfile.parse(req.body);
API4: Unrestricted Resource Consumption
Without limits, one client can exhaust CPU, memory, database connections or paid third-party services such as SMS, email or LLM calls. For AI-powered features this is now a real cost risk, because every request may trigger a paid model call.
- Apply rate limits per user, per token and per IP. Laravel's
RateLimiterwith thethrottlemiddleware, orexpress-rate-limitbacked by Redis in Node.js, covers most needs. - Cap page sizes, upload sizes, request body sizes and query complexity (especially for GraphQL).
- Set timeouts on outgoing HTTP calls and database queries.
- Put per-account budgets on expensive operations such as LLM prompts, exports and bulk SMS.
// AppServiceProvider::boot()
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(120)->by($request->user()?->id ?: $request->ip());
});
RateLimiter::for('ai', function (Request $request) {
return Limit::perDay(200)->by($request->user()->current_team_id);
});
API5: Broken Function Level Authorization (BFLA)
BOLA is about which object; BFLA is about which action. A normal user calls DELETE /api/users/77 or POST /api/admin/export and it works, because the endpoint only checks that someone is logged in. Fix it by protecting routes by role or permission at the routing layer, not inside the UI:
- Laravel: group admin routes behind middleware such as
can:gates or a roles package, and keep admin APIs on a separate route file or prefix. - Node.js: add a reusable
requireRole('admin')or permission middleware and apply it to whole routers, then add tests that call admin endpoints as a normal user and expect 403.
API6: Unrestricted Access to Sensitive Business Flows
Some flows are technically fine but harmful when automated: buying limited stock, creating accounts, posting reviews, redeeming coupons or booking slots. Protect them with business-level controls such as per-account purchase limits, device fingerprinting, CAPTCHA or proof-of-work on suspicious traffic, and alerts when patterns look automated.
API7: Server-Side Request Forgery (SSRF)
Any feature that fetches a URL supplied by the user (webhooks, link previews, "import from URL", image fetching, AI tools that browse) can be abused to reach internal services or cloud metadata endpoints. Allow-list destinations where possible, resolve the hostname and block private and link-local ranges, disable redirects or re-check them, and on AWS enforce IMDSv2 so metadata credentials are not one GET away.
API8: Security Misconfiguration
Common examples: APP_DEBUG=true in production, verbose stack traces, permissive CORS (* with credentials), missing TLS, default credentials on admin panels, open storage buckets and outdated packages. A short, repeatable checklist in your deployment pipeline catches most of these. Run composer audit and npm audit (or a tool like Dependabot) on every build.
API9: Improper Inventory Management
You cannot protect endpoints you have forgotten about. Old /v1 routes, staging APIs exposed to the internet and undocumented debug endpoints are frequent entry points. Keep an OpenAPI specification as the source of truth, generate it from code where possible, retire old versions on a schedule, and keep staging behind a VPN or authentication.
API10: Unsafe Consumption of APIs
Your API also consumes other APIs: payment providers, shipping carriers, CRMs and LLM providers. Treat their responses as untrusted input. Validate the shape of what comes back, verify webhook signatures (for example Stripe's signing secret), use timeouts and circuit breakers, and never pass third-party content straight into SQL, shell commands or, for AI features, straight into privileged tool calls.
A practical API security baseline for 2026
If you only have a week, this baseline covers the highest-impact items for a typical Laravel or Node.js product:
- Authorization on every endpoint. Policies or middleware for actions, scoped queries for objects, and automated tests using two different users.
- Explicit input and output contracts. Form Requests or Zod schemas in, API Resources or DTOs out.
- Proven authentication. Sanctum, Passport or a managed identity provider, short token lifetimes and MFA for admins.
- Rate limits and budgets. Global, per-user and per-feature, with tighter limits on auth and AI endpoints.
- Secure configuration. Debug off, strict CORS, security headers, secrets in a vault or environment, not in Git.
- Dependency and secret scanning in CI on every pull request.
- Logging and alerting for auth failures, 403 spikes and unusual data volumes, without logging tokens or personal data.
- An up-to-date API inventory with an OpenAPI spec and a plan to retire old versions.
Testing your API like an attacker
Most of these flaws are invisible in normal QA because testers use one account and the happy path. Add a few habits:
- Two-account tests: create user A and user B in feature tests, then try to read, update and delete B's objects as A. Expect 403 or 404 every time.
- Role tests: call every admin route as a normal user.
- Payload tests: send extra fields such as
role,priceandteam_idand confirm they are ignored or rejected. - Automated scanning: run tools such as OWASP ZAP against staging, and consider a professional penetration test before major launches or enterprise deals.
// Pest / PHPUnit feature test for BOLA
it('does not let one team read another team\'s order', function () {
$alice = User::factory()->create();
$bobOrder = Order::factory()->create(); // belongs to another team
$this->actingAs($alice, 'sanctum')
->getJson("/api/orders/{$bobOrder->id}")
->assertForbidden();
});
What about AI agents calling your API?
As more products expose APIs to AI agents and LLM tool calls, the same Top 10 still applies, but the impact grows. An agent may chain many calls quickly, retry aggressively and act on content it read elsewhere. Give agent integrations their own scoped tokens with the minimum abilities, apply strict rate limits and budgets, require human confirmation for destructive or financial actions, and log every tool call. If you are adding AI features to your own product, our guide on integrating AI into a Laravel or Node.js app covers the architecture side.
Summary
Most API breaches do not come from exotic exploits. They come from a missing ownership check, an over-generous response, an admin route without a role check or an endpoint with no rate limit. The OWASP API Security Top 10 gives you a clear map. In Laravel, policies, Form Requests, API Resources, Sanctum and the rate limiter cover most of it. In Node.js, scoped queries, schema validation, strict JWT verification and Redis-backed rate limiting do the same job. Add two-account tests and a short deployment checklist, and you will close the gaps attackers try first. For the wider application side, see our Laravel web application security checklist.
FAQ
Is the OWASP API Security Top 10 different from the OWASP Top 10?
Yes. The main OWASP Top 10 covers web application risks in general, while the API Security Top 10 focuses on problems specific to APIs, such as object-level and property-level authorization, resource consumption and API inventory. Most products should use both.
Are UUIDs enough to prevent BOLA?
No. UUIDs make IDs harder to guess, but IDs leak through logs, URLs, shared links and other responses. You still need an ownership or permission check on every request that uses an ID.
Should I use Laravel Sanctum or Passport for my API?
Sanctum suits most first-party SPAs and mobile apps that need simple, scoped tokens. Passport is the better fit when you need a full OAuth2 server, for example to let third-party developers connect to your platform.
How often should we run a penetration test?
A common approach is at least once a year and before major releases, large architecture changes or enterprise customer reviews, with automated scanning and security tests running on every build in between.
Do rate limits really matter for small products?
Yes, especially if your API sends emails or SMS or calls paid AI models. Without limits, a single script can create real costs or take your service offline, regardless of how many users you have.
Can you audit an existing Laravel or Node.js API?
Yes. A typical review covers authorization on every route, input and output handling, authentication setup, rate limiting, configuration and dependencies, and ends with a prioritised list of fixes.
Written by
Prakash Tank
Founder & Full-Stack Architect, DayDecoder
Prakash started as a certified Laravel developer and grew into a full-stack architect who owns a project end to end: requirements, architecture, development, deployment, and support. He designs event-driven systems with...
More about Prakash