Security Audit Report

FakeStoreAPI requires auth on HEAD but not GET. That's not a feature.

https://fakestoreapi.com/products/1
75 C Good security posture
10 findings
2 Critical 3 High 1 Medium 4 Low
Share
01

About This API

FakeStoreAPI is a public, no-auth-required REST API that serves a fixed catalog of fake e-commerce products — twenty items across categories like men's clothing, jewelry, and electronics. Each product has a title, price, description, category, image URL, and a rating block. It was built to be the first backend in a React tutorial. You can fetch('https://fakestoreapi.com/products'), get twenty products back, render them as cards, and you've just built "a store" in a fifteen-minute video.

The API's reach is hard to overstate. The FreeCodeCamp e-commerce tutorial uses it. Every React-Query tutorial with a "data fetching" chapter uses it. A large percentage of "build a shop" YouTube playlists point at it. The Udemy "Complete React Course" sections on data fetching use it. If you ever shipped a Pluralsight, Egghead, Scrimba, or TraversyMedia tutorial involving useEffect-to-fetch product-grid, you've probably had FakeStoreAPI open.

Beyond the product catalog, FakeStoreAPI ships a set of endpoints that make the mock feel more realistic: /auth/login returns a JWT-shaped token, /carts accepts POST/GET/PUT for shopping carts, and /users echoes fake user records. These endpoints are deliberately stateless — POST requests appear to succeed but don't persist. The mock is doing exactly what a mock should do. The problem is that the surface-level behavior is convincing enough that learners sometimes don't realize they're looking at a mock, and carry its patterns into code where persistence, authentication, and authorization matter.

This audit is written for the student or junior developer who built their first e-commerce app against FakeStoreAPI, and who is about to build their second one against a real backend. The findings are mostly not about FakeStoreAPI's safety — they're about what you should unlearn from the experience.

02

Threat Model

FakeStoreAPI itself is a safe target. The data is synthetic, the endpoints are stateless, no user ever logs in for real, no cart is ever real. The threat model is not about attackers compromising FakeStoreAPI; it's about what patterns propagate into the real e-commerce apps that students build after completing the tutorials.

The propagated cart pattern

The most common pattern taught against FakeStoreAPI is "when the user clicks Add to Cart, POST to /carts." The mock returns 200 OK with a cart ID and a date field. Students see the 200, check it off as "cart works," and ship. What they never test — because the mock never fails — is whether the cart actually persisted. On real e-commerce backends, carts require authentication, idempotency, price-at-time-of-add locking, and inventory reservation. None of these are present in FakeStoreAPI's mock, and a student shipping to production without adding them loses orders silently under load.

The propagated login pattern

FakeStoreAPI's POST /auth/login returns a JWT-shaped string like eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.... Students store it in localStorage and attach it to future requests via Authorization: Bearer <token>. This is the pattern every tutorial teaches. But the mock doesn't verify the JWT on any protected endpoint — it's just a string — so students learn "store token in localStorage" without learning the countermeasures (XSS exfiltration, token rotation, refresh-token handling, sliding expiry) that a real JWT flow requires. The pattern survives the tutorial; the countermeasures don't.

The propagated CORS pattern

Every response has Access-Control-Allow-Origin: *, which is what makes the tutorial work from any localhost or Codesandbox origin. When the student moves to a real backend, they often copy the CORS config from the tutorial into their own Express or FastAPI server. For an authenticated e-commerce API that handles payment intents and user data, wildcard CORS is a browser-reachable attack surface.

The inconsistent auth finding specifically

HEAD returns 401 while GET returns 200 on the same path. This is a genuine implementation quirk — probably a middleware ordering bug. For the audit it means an automated script walking HEAD-to-discover-endpoints gets a different set than one walking GET, which is unusual enough to be worth calling out. On any real API, GET and HEAD should have symmetric authentication because HEAD is definitionally "GET without the body."

03

Methodology

middleBrick ran a black-box scan against https://fakestoreapi.com/products/1. Ten security checks executed across OWASP API Top 10 categories: authentication, authorization (BOLA/BFLA/property), input validation, CORS, rate limiting, and inventory management. LLM-specific probes did not fire — FakeStoreAPI's fingerprint is plain REST with no inference-related signals.

The multi-method probe is what surfaced the inconsistent-auth finding. middleBrick sent HEAD, GET, and OPTIONS to the same URL. HEAD returned 401 with a WWW-Authenticate challenge; GET returned 200 with the product JSON; OPTIONS returned 204 with an Allow header listing GET, POST, PUT, DELETE, PATCH. The asymmetry is the finding — in a correctly-configured API, HEAD is identical to GET minus the body, so their auth behavior should match.

Active IDOR probing compared /products/1 against /products/2. The response schemas matched 7-of-7 keys (id, title, price, description, category, image, rating), and both returned 200 with different product data. The scanner flagged this as confirmed IDOR via adjacent-ID enumeration, correctly. On a public product catalog this is the intended behavior; the flag is structural rather than actionable.

No destructive methods were issued. No authentication was attempted. The Authorization header was never sent, not even the fake tokens that the documented login flow produces.

04

Results Overview

FakeStoreAPI pulled a grade of C with a score of 75. Ten findings: two critical, three high, one medium, four low. That distribution is closer to ReqRes's (which had seventeen findings) than to PokéAPI's (twelve, mostly mock-structural) despite FakeStoreAPI being smaller than either.

The two CRITICAL findings are the structural pair — no authentication required on GET, and confirmed IDOR via sequential product IDs. Both are intentional for a public product catalog. The finding we keep flagging across every public-mock audit in this series: your scanner will raise these as CRITICAL, and it's worth having language to explain why they're by design rather than silencing the severity.

The three HIGH findings are the interesting ones. Inconsistent authentication across HTTP methods is the unique-to-FakeStoreAPI quirk — HEAD requires auth, GET doesn't. Wildcard CORS is structural to any public mock. Missing rate-limit headers is structural to any public mock that hasn't invested in the operational layer yet.

The one MEDIUM finding — numeric object ID in the response body — is the same correct-for-a-mock, would-be-BOLA-on-a-real-API signal we've seen before. Four LOWs round it out: missing security headers, DELETE/PUT/PATCH advertised via OPTIONS, no URL versioning, and X-Powered-By: Express.

What's conspicuously absent is any finding on FakeStoreAPI's login flow, cart behavior, or user endpoints. That's because the scanner didn't probe those paths — it hit /products/1, not /auth/login or /carts. A full spec-driven scan would almost certainly find more: the mock's login returns a token without verifying credentials, the cart POST accepts arbitrary bodies, and the user endpoints echo whatever you send. Those weren't in this scan's scope, but they inform the threat-model and remediation sections of this write-up because they are what students consume from the API.

05

Detailed Findings

Critical Issues 2
CRITICAL CWE-306

API accessible without authentication

The endpoint returned 200 without any authentication credentials.

Remediation

Implement authentication (API key, OAuth 2.0, or JWT) for all API endpoints.

authentication
CRITICAL CWE-639

Sequential IDs with confirmed IDOR (ID +1)

Path contains numeric IDs (1) — easily enumerable by attackers. Changing ID from 1 to 2 returned 200. Response schemas match (7/7 keys identical) — strong IDOR evidence.

Remediation

Use UUIDs or non-sequential identifiers. Implement object-level authorization checks.

bolaAuthorization
High Severity 3
HIGH CWE-306

Inconsistent authentication across HTTP methods

GET returns 200 without auth, but some methods (HEAD) require authentication.

Remediation

Apply authentication uniformly across all HTTP methods for each endpoint.

authentication
HIGH CWE-942

CORS allows all origins (wildcard *)

Access-Control-Allow-Origin is set to *, allowing any website to make requests.

Remediation

Restrict CORS to specific trusted origins. Avoid wildcard in production.

inputValidation
HIGH CWE-770

Missing rate limiting headers

Response contains no X-RateLimit-* or Retry-After headers. Without rate limiting, the API is vulnerable to resource exhaustion attacks (DoS, brute force, abuse).

Remediation

Implement rate limiting (token bucket, sliding window) and return X-RateLimit-Limit, X-RateLimit-Remaining, and Retry-After headers.

resourceConsumption
Medium Severity 1
MEDIUM CWE-639

IDOR risk: numeric object ID in response body

Response body contains 1 numeric ID field(s) (id:1). Enumerable object IDs enable IDOR / insecure direct object reference attacks when per-object authorization is missing.

Remediation

Use UUIDs for object identifiers. Verify per-object authorization on every request. Never expose internal IDs for resources the user doesn't own.

bolaAuthorization
Low Severity 4
LOW CWE-693

Missing security headers (4/4)

Missing: HSTS — protocol downgrade attacks; X-Content-Type-Options — MIME sniffing; X-Frame-Options — clickjacking; Cache-Control — sensitive response caching.

Remediation

Add the following headers to all API responses: strict-transport-security, x-content-type-options, x-frame-options, cache-control.

authentication
LOW CWE-650

Dangerous HTTP methods allowed: DELETE, PUT, PATCH

The server advertises support for methods that can modify or delete resources.

Remediation

Only expose HTTP methods that are actually needed. Disable TRACE, and restrict DELETE/PUT/PATCH.

inputValidation
LOW CWE-1059

No API versioning detected

The API URL doesn't include a version prefix (e.g., /v1/) and no version header is present.

Remediation

Implement API versioning via URL path (/v1/), header (API-Version), or query parameter.

inventoryManagement
LOW CWE-200

Technology exposed via X-Powered-By: Express

The X-Powered-By header reveals framework details.

Remediation

Remove the X-Powered-By header in production.

inventoryManagement
06

Attacker Perspective

The attacker approaching FakeStoreAPI has no reason to attack FakeStoreAPI. The service holds no data, no accounts, no state. The attacker's interesting role here is as a diagnostician of downstream code — the real stores built against FakeStoreAPI's patterns.

Read the tutorial, find the pattern

The most productive attack is to read the five most-viewed "build a React e-commerce app" tutorials on YouTube and extract the patterns they teach. You'll end up with a shortlist: localStorage for auth tokens, POST /carts without idempotency keys, no stock or reservation check on checkout, product prices rendered from the API response rather than re-validated server-side on add-to-cart, and CORS wildcard in the backend code. Then search GitHub for repos that match three or more of those patterns simultaneously. The ones with active deployments and real payment integrations are the real targets.

XSS-to-token in tutorial-derived apps

Because the tutorial stores the login token in localStorage, any XSS on a tutorial-derived store steals the token. A student who deployed their FakeStoreAPI-based tutorial to my-shop.vercel.app and then swapped the backend for a real one without revisiting the token storage has built an XSS-to-full-account-takeover chain. The fix is to move tokens to HttpOnly cookies or to a short-lived in-memory pattern with refresh tokens; neither is taught in the canonical tutorials.

Race the cart on a real store

The mock cart endpoint returns immediately with 200. Students who promote this pattern to a real backend frequently skip the idempotency-key check and the price-at-time-of-add lock. An attacker then races the add-to-cart with price changes between check and commit: read current price, add-to-cart, observe that the price in the cart is captured at add-time rather than verified at checkout. On a real store this is free money until the first audit notices the price delta.

07

Analysis

The inconsistent-auth-across-methods finding is the technically interesting one. On HEAD:

$ curl -I https://fakestoreapi.com/products/1
HTTP/2 401
www-authenticate: Basic realm="Access restricted"

On GET:

$ curl https://fakestoreapi.com/products/1
HTTP/2 200
content-type: application/json; charset=utf-8

{"id":1,"title":"Fjallraven - Foldsack No. 1 Backpack...","price":109.95,...}

The probable cause is a middleware ordering bug — the Basic-Auth middleware is attached before the path handler in a way that triggers on HEAD but is skipped by the handler's GET short-circuit. This isn't dangerous on FakeStoreAPI (there's nothing to protect anyway), but the asymmetry is a real implementation defect. It also breaks clients that use HEAD to cheaply check for resource existence before issuing a GET — they get a 401 that isn't actually about authorization.

The CORS and rate-limit findings are what we've seen on every spearhead. Access-Control-Allow-Origin: *, no X-RateLimit-*, no Retry-After. Both are correct-for-a-mock and copy-this-pattern-at-your-peril for anyone building a real store.

The sequential-ID / IDOR pair:

GET /products/1 → 200 (Fjallraven Backpack)
GET /products/2 → 200 (Mens Casual Premium Slim Fit T-Shirts)

Both return identically-shaped JSON with different product content. On a public catalog this is the whole point. On a catalog gated by user ownership ("show me products I have permission to buy") the same pattern would enable IDOR enumeration of the hidden catalog. The mock doesn't gate, so the finding is structural.

08

Industry Context

FakeStoreAPI sits in the same taxonomy as JSONPlaceholder and DummyJSON: the public mock whose explicit job is to return plausible-looking data for teaching. Where it differs from both is the domain — e-commerce specifically — and the level of fidelity. FakeStoreAPI is plausible enough that students can build something that looks like a store rather than something that looks like a demo. That's great for teaching and dangerous for propagation.

Compared to real e-commerce backends — Shopify's Storefront API, Commerce.js, Swell — FakeStoreAPI's posture is obviously different and should be. The real backends enforce authentication, carry session state, validate product prices on checkout, enforce stock reservation, apply tax and shipping calculations, and log every transaction. Tutorials using FakeStoreAPI necessarily skip all of these because the mock doesn't support them. A student who learned e-commerce from a FakeStoreAPI tutorial and then deploys a real Shopify integration has an accurate mental model of the front-end flow and zero exposure to the back-end complexity.

Compliance context: actual e-commerce APIs are in scope for PCI-DSS (any handling of card data), GDPR / LGPD / PDPL (any handling of user PII), and often local tax-invoice regulations (in LATAM: SAT Mexico, DGII in Dominican Republic). None of these apply to FakeStoreAPI itself. All of them apply to the real store the student ships next. The gap between tutorial and production is larger than most tutorials acknowledge.

OWASP API Top 10 mapping for what this audit finds: API1 (intentional no-auth), API3 (BOLA via sequential IDs, intentional), API5 (BFLA potential on inconsistent-auth), API8 (security misconfiguration via wildcard CORS and missing rate-limit headers), API9 (versioning and framework disclosure). Five of ten categories touched. What's not touched but matters downstream: API2 (broken authentication in the derived apps using localStorage tokens), API6 (unrestricted access to sensitive flows in cart add/checkout patterns), API4 (unrestricted resource consumption in unpaginated product listings).

09

Remediation Guide

Inconsistent authentication across HTTP methods

Ensure HEAD and GET on the same path go through the same auth middleware and return consistent status codes. Usually a middleware ordering fix.

// Express: attach auth before the route, not before the method handler
function publicReadOnly(req, res, next) {
  // No auth required — applies identically to GET and HEAD
  next();
}
app.use('/products', publicReadOnly);
app.get('/products/:id', (req, res) =&gt; res.json(getProduct(req.params.id)));
app.head('/products/:id', (req, res) =&gt; {
  if (!getProduct(req.params.id)) return res.status(404).end();
  res.status(200).end();
});

Missing rate-limit headers

Emit X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After using the documented policy.

import rateLimit from 'express-rate-limit';
app.use(rateLimit({
  windowMs: 60_000,
  max: 200,
  standardHeaders: 'draft-7',
  legacyHeaders: false
}));

Missing security headers

Add HSTS, X-Content-Type-Options, X-Frame-Options, and a sensible Cache-Control via helmet.

import helmet from 'helmet';
app.use(helmet({
  contentSecurityPolicy: false, // JSON API
  hsts: { maxAge: 31536000, includeSubDomains: true },
  noSniff: true,
  frameguard: { action: 'deny' }
}));

Consumer pattern: don't inherit the localStorage token

For derived real-store apps, store auth tokens in HttpOnly+Secure cookies rather than localStorage. Eliminates the XSS-to-token-theft chain.

// Server sets cookie
res.cookie('session', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 1000 * 60 * 60 * 24 * 7
});

Consumer pattern: idempotent cart add

Real cart endpoints must accept an idempotency key on POST and detect retries. FakeStoreAPI doesn't need this, real stores do.

app.post('/carts/add', async (req, res) =&gt; {
  const key = req.get('Idempotency-Key');
  if (!key) return res.status(400).json({ error: 'Idempotency-Key required' });
  const existing = await kv.get(`idem:${key}`);
  if (existing) return res.status(200).json(existing);
  const result = await addToCart(req.user.id, req.body);
  await kv.put(`idem:${key}`, result, { ttl: 86400 });
  res.status(201).json(result);
});

Consumer pattern: server-side price validation on checkout

Never trust client-sent prices on checkout. Re-read product prices from your own database at checkout time and compare against the cart.

app.post('/checkout', async (req, res) =&gt; {
  const cart = await getCart(req.user.id);
  for (const item of cart.items) {
    const current = await getProductPrice(item.productId);
    if (current !== item.priceAtAdd) {
      return res.status(409).json({ error: 'price_changed', productId: item.productId });
    }
  }
  // proceed to payment intent creation
});

X-Powered-By framework disclosure

One line.

app.disable('x-powered-by');
10

Defense in Depth

If you maintain FakeStoreAPI or similar mock e-commerce APIs, the highest-leverage change is to fix the inconsistent-auth quirk. HEAD and GET should respond the same on the same path — if both are public, 200 on both; if both are protected, 401 on both. This is a middleware fix, not a rearchitecture. Adding rate-limit headers matching your documented policy is the next one. Beyond that, every finding is either structural-to-being-a-mock or cosmetic (suppress X-Powered-By, add X-Content-Type-Options).

A more ambitious defense is to annotate the published behavior. A docs/teaching-scope.md alongside the README that explicitly documents which patterns FakeStoreAPI demonstrates and which patterns students should not inherit would measurably reduce the real-world damage. Something like: "The POST /carts endpoint is stateless and always returns 200. In production, your cart endpoint needs authentication, idempotency keys, inventory reservation, and price-at-time-of-add locking." One paragraph in the right place could save ten thousand broken stores.

If you are a tutorial maker consuming FakeStoreAPI, the defense is to be explicit in your teaching. When you show POST /carts returning 200, narrate what's missing: "in a real backend, this endpoint would also check the user's session, lock the inventory, and return an idempotency token." This is a ten-second addition to a fifteen-minute tutorial and it's the difference between teaching a CRUD pattern and teaching e-commerce.

If you are a student who learned e-commerce from a FakeStoreAPI tutorial, the defense is to assume that every endpoint in your next real store needs more than what the tutorial showed. Authentication checks on every read and write. Idempotency on every write. Server-side price validation on every cart add. Rate-limit headers on every response. A tight CORS allowlist, not a wildcard. These are not optional features; they are what separates a toy from a product.

11

Conclusion

FakeStoreAPI is a C-grade mock e-commerce API, which for a mock is fine. The findings break down into two categories: structural (correct for a mock, flagged because scanners can't know that), and instructive (patterns the API teaches that shouldn't be inherited into real stores). The one finding that's neither — the HEAD-requires-auth-while-GET-doesn't quirk — is a real bug worth fixing, though low-impact because the resource behind the inconsistency is public anyway.

The real audience for this case study is not the FakeStoreAPI maintainer. It's the student who just finished a React e-commerce tutorial and is about to build a real store. FakeStoreAPI taught you how to render a product grid, how to call POST /carts from a button click, and how to store a returned token in localStorage. It did not teach you authentication, authorization, idempotency, inventory reservation, price validation, CORS allowlists, or rate limiting — because the mock doesn't need any of those. Your real store does.

The specific things to unlearn: that a 200 from POST /carts means the cart persisted, that a token in localStorage is an acceptable auth storage pattern, that wildcard CORS is a reasonable default, that sequential product IDs are fine on a catalog your users have scoped access to, and that HEAD and GET can disagree on authentication. Unlearn those five things and your real store is a step closer to production-safe.

Frequently Asked Questions

Is FakeStoreAPI safe to use in a tutorial?
Yes. The mock does exactly what it's supposed to do and holds no data that could be exploited. The question isn't safety — it's whether the patterns you're teaching on top of the mock are patterns your students should inherit into their real apps.
Why does HEAD require auth but GET doesn't? Is this a security issue?
It's an implementation defect, probably a middleware ordering bug. It isn't directly exploitable — the resource behind both methods is public — but it breaks clients that use HEAD for cheap existence checks, and it's the kind of asymmetry a scanner correctly flags.
What should I unlearn from a FakeStoreAPI tutorial before building a real store?
Five things: that a 200 response from POST /carts means the cart was persisted, that a token in localStorage is acceptable auth storage, that wildcard CORS is a reasonable default, that sequential product IDs work on ownership-gated catalogs, and that HEAD and GET can disagree on authentication. Unlearn those and your real store is substantially closer to production-safe.
Does FakeStoreAPI's login endpoint return a real JWT?
It returns a JWT-shaped string that the mock's own endpoints do not verify. For the mock's purpose this is fine. The pattern that's dangerous is teaching students to store the string in localStorage without teaching the XSS countermeasures that real JWT flows require.
Should I copy FakeStoreAPI's CORS config into my real Express backend?
No. Wildcard CORS is the first thing you should change when moving from a mock to a real backend. Your real store should have an explicit allowlist of origins — your storefront, your admin panel, your payment-processor webhooks — and nothing else.
Is it safe to deploy a FakeStoreAPI-based tutorial app to Vercel / Netlify / Cloudflare Pages?
For a learning project, yes. For anything customer-facing, no — even though the backend is a mock, the frontend patterns you're deploying include the localStorage-token and wildcard-CORS habits that become exploitable the moment you swap the backend for a real one.
What does a 'real store' actually need that FakeStoreAPI doesn't teach?
Authentication that the server verifies on every protected endpoint, authorization that scopes reads and writes to the user's resources, idempotency keys on all mutating endpoints, inventory reservation on cart add, server-side price validation on checkout, rate limiting with published headers, explicit CORS allowlists, PCI-compliant payment flow (never hold card data on your server), audit logs on every transaction, and a tax-calculation integration. Roughly none of these are in a tutorial.