Security Academy→Course→API Security
Modul 4 / 12

API Security

Inspiser requests i DevTools og skill frontend-validering fra server-validering.

DevTools → Network: hva du ser etter

Filtrer på Fetch/XHR, gjør én handling i appen, og gå gjennom hver request.

  • ›Endpoint og HTTP-metode: finnes det DELETE/PATCH du ikke visste om?
  • ›Request headers: Authorization, apikey, cookies — hvilket token brukes?
  • ›Request body: ID-er, roller, priser, user_id — alt dette kan endres
  • ›Response: returneres flere felter enn UI viser? (password_hash, email, interne flagg)
  • ›Feilrespons: stack traces, SQL-feil, interne stier
  • ›Rate limiting: kan du kalle login/AI-endpoint 100 ganger på 10 sekunder?

Frontend validation vs server-side validation

Frontend-validering er UX: rask tilbakemelding. Server-validering er sikkerhet: den eneste som faktisk gjelder.

Test ved å sende requesten uten å gå via skjemaet (Copy as fetch i Console og endre body).

Vulnerable · ts
// Kun i skjemaet:
<input type="number" min={1} max={10} />

// Server stoler på klienten
.handler(async ({ data }) => {
  await db.from("orders").insert({
    qty: data.qty, price: data.price, user_id: data.userId,
  });
});
Secure · ts
const Order = z.object({
  productId: z.string().uuid(),
  qty: z.number().int().min(1).max(10),
});
.inputValidator(Order)
.handler(async ({ data, context }) => {
  const price = await getPrice(data.productId); // fra DB, ikke klient
  await context.supabase.from("orders").insert({
    product_id: data.productId, qty: data.qty,
    price, user_id: context.userId,           // fra session
  });
});

Spill av en request fra Console

Høyreklikk request → Copy → Copy as fetch. Lim inn i Console og endre verdier.

await fetch("/_serverFn/createOrder", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ data: { productId: "…", qty: -50, price: 0 } }),
}).then(r => r.status)
// Forventet: 400. Fikk du 200, finnes ingen server-validering.
Kontroller i din egen app

Quiz

Scenario 1

Checkout sender { items, total } til server, som oppretter betaling med total. Hva tester du først?

Scenario 2

API-responsen for /api/users/me inneholder stripe_customer_id og is_admin, selv om UI bare viser navn. Er det en finding?