CV laden

Security

Das Token gehört nicht nach JavaScript

Sanctum schreibt auth_token als httpOnly. Das Login-JSON hat kein Token-Feld. Kann die Seite die Session lesen, schickt XSS sie weg.

Das erste Login-JSON dieser API hatte ein Token-Feld. Vue speicherte es. Der Body landete außerdem im nginx-Access-Log und im Network-Panel. localStorage liest jedes Skript auf dem Origin. Eine eingeschleuste Dependency oder ein nicht escaptes Feld reicht, um das Token zu kopieren, und es bleibt bis zum Ablauf gültig.

Ein httpOnly-Cookie ist aus JavaScript nicht erreichbar. Dieselbe Injection liest auth_token nicht. Die Login-Route setzt das Cookie jetzt mit httpOnly, secure und SameSite lax. API und Vue-SSR teilen die Site, der Browser sendet das Cookie ohne CORS-Credential-Tricks. Der JSON-Body eines erfolgreichen Logins hat keinen Token-Key.

Nutzer brauchten den String nicht. XSS schon. Der Host braucht einen Session-Store, keine Kopie in JS und eine im Cookie.

Token nicht in den JSON-Body. Logs und Client-State würden eine Kopie behalten.
Token nicht in den JSON-Body. Logs und Client-State würden eine Kopie behalten.

CSRF zählt weiter

Cookies fahren auf jedem Request, den der Browser an diese Site schickt. XSS kann das Cookie nicht lesen. Ein Formular auf einer anderen Site kann trotzdem einen Write auslösen, wenn CSRF nicht dazwischensteht. Sanctum liest das CSRF-Cookie und erwartet den Header bei unsicheren Methoden.

Logout löscht das Token auf dem Server. Das Cookie im Browser zu löschen reicht nicht. Ein gestohlenes Cookie, das noch in Redis oder in der Token-Tabelle liegt, würde weiterlaufen. Nach Logout muss /api/user 401 liefern.

Ich fand einen Helper, der nach dem Login das User-Objekt in sessionStorage legte, inklusive eines Token-Keys aus einer alten Response-Form. Das Cookie war schon httpOnly. Der Helper stellte das Leak wieder her. Im Storage bleiben Name und Locale, nichts, womit man /api aufruft.

Cookie mit CSRF koppeln. Sanctum erwartet den Header bei unsicheren Methoden.
Cookie mit CSRF koppeln. Sanctum erwartet den Header bei unsicheren Methoden.

Tests, die das halten

Vier Assertions fangen die meisten Regressionen, die ich hier gesehen habe. Login gibt kein Token-Feld zurück. Das Cookie trägt httpOnly. Ein Write ohne CSRF-Header wird abgelehnt. Nach Logout ist der User-Endpunkt 401.

Das Frontend schreibt auth_token nie nach localStorage, sessionStorage oder in ein Pinia-Persist. Legt ein späterer Helper das User-Objekt in Storage, darf das Objekt das Token nicht enthalten. Die Session ist das Cookie. Alles andere ist ein Anzeigename.

SSR darf das Cookie nicht in den Seitenstate drucken. Ein serialisierter User im HTML ist in Ordnung. Ein serialisiertes Geheimnis ist dasselbe Leak wie localStorage, mit längerer Cache-Lebensdauer, falls nginx dieses HTML je speichert.

Was ich daraus mitnehme

Ich grepe Login-JSON, Pinia-Persist und das SSR-Payload nach einem Token-Feld. Logs und HTML sind Kopien der Antwort. Steht der String dort, braucht XSS kein localStorage.

Einem Logout, das nur das Browser-Cookie löscht, traue ich nicht. Die Server-Zeile muss sterben. SameSite allein reicht nicht. CSRF sitzt weiter auf unsicheren Methoden.

Die Regel, die ich behalte: nur das Sanctum-Cookie, httpOnly, nie im JSON-Body. Das Frontend speichert einen Anzeigenamen. /api/user nach Logout ist 401.

Zurück zu Notizen