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?