Security Academy→Course→Authentication
Modul 2 / 12

Authentication

Login, sessions, tokens og de typiske AI-feilene rundt dem.

Hva du kontrollerer

Gå gjennom hvert punkt i DevTools → Application og Network.

  • ›Login: generiske feilmeldinger (ikke «bruker finnes ikke»), rate limiting
  • ›Logout: blir token/refresh token faktisk ugyldig, eller bare slettet lokalt?
  • ›Sessions/cookies: Secure, HttpOnly, SameSite, utløpstid
  • ›Password reset / magic links: engangsbruk, kort levetid, redirect-URL låst til eget domene
  • ›OAuth: state-parameter, redirect allowlist, ingen token i URL som logges
  • ›JWT: signatur verifiseres på server, alg låst, exp sjekkes, ikke stol på claims som brukeren kan påvirke (user_metadata)
  • ›Refresh tokens: rotasjon, revokering ved logout/passordbytte

Klassikeren: skjult Admin-knapp

AI-verktøy løser «bare admin skal se dette» ved å skjule knappen. Endpointet bak er fortsatt åpent for alle med en gyldig session — eller helt uten.

Å skjule UI er brukervennlighet, ikke sikkerhet. Test alltid endpointet direkte.

Vulnerable · ts
// UI
{user.role === "admin" && <Button onClick={() => deleteUser(id)}>Delete</Button>}

// server
export const deleteUser = createServerFn({ method: "POST" })
  .inputValidator((d: { id: string }) => d)
  .handler(async ({ data }) => {
    await supabaseAdmin.from("profiles").delete().eq("id", data.id);
  });
Secure · ts
export const deleteUser = createServerFn({ method: "POST" })
  .middleware([requireSupabaseAuth])
  .inputValidator(z.object({ id: z.string().uuid() }))
  .handler(async ({ data, context }) => {
    const { data: isAdmin } = await context.supabase
      .rpc("has_role", { _user_id: context.userId, _role: "admin" });
    if (!isAdmin) throw new Response("Forbidden", { status: 403 });
    const { supabaseAdmin } = await import("@/integrations/supabase/client.server");
    await supabaseAdmin.from("profiles").delete().eq("id", data.id);
  });

Rollen hentes fra en egen tabell på server — aldri fra localStorage, user_metadata eller request body.

Slik verifiserer du at backend håndhever

Logg inn som vanlig bruker, kopier en admin-request fra Network (høyreklikk → Copy as cURL) og kjør den med ditt eget token.

# Forventet: 401 uten token, 403 som vanlig bruker
curl -i -X POST https://din-app.test/_serverFn/deleteUser \
  -H "Authorization: Bearer <TOKEN_FOR_VANLIG_BRUKER>" \
  -H "Content-Type: application/json" \
  -d '{"data":{"id":"<annen-bruker-id>"}}'

Rolle i token eller localStorage

Hvis du ser role: "user" i localStorage eller i et JWT-claim brukeren kan redigere, endre det og last siden på nytt. Om serveren nå gir mer tilgang, har du en kritisk finding.

Kontroller i din egen app
Labs for denne modulen

Quiz

Scenario 1

Lovable har generert en Admin-knapp som bare vises dersom user.role === "admin". API-endepunktet /api/delete-user kontrollerer imidlertid ikke rollen. Er funksjonen sikker?

Scenario 2

Appen lagrer isAdmin: true i localStorage etter login og bruker det til å vise adminpanelet, som henter data med supabaseAdmin i en server function uten auth. Hva er alvorligst?