Communitygithub.com

rbikbov/frontend-starter

Authorisierung und Sitzungsverwaltung im Frontend mit Next.js (App Router): Tokenspeicherung, BFF mit HttpOnly-Cookie, Race-Condition-freier Refresh, Erkennung von „nicht autorisiert“ (inkl. GraphQL mit 200-Status), Prüfungen in proxy.ts und Data Access Layer, sicherer Redirect nach Login, Logout ohne Datenleck, Token in Passwort-Reset-Links, Login- und Wiederherstellungsformulare.

Was ist frontend-starter?

Dieses Skill bietet eine umfassende, praxisorientierte Anleitung zur Implementierung von Authentifizierung und Sitzungsverwaltung in Next.js-Anwendungen (App Router). Es behandelt die gesamte Kette von der Token-Speicherung über sichere Cookie-Konfiguration bis hin zur Implementierung eines BFF (Backend for Frontend) mittels Route Handlern. Der Fokus liegt auf erprobten Sicherheitspraktiken: HttpOnly-Cookies, SameSite-Attribute, Single-Flight-Refresh-Mechanismus zur Vermeidung von Race Conditions, und eine klare Trennung von optimistischen Prüfungen im Proxy/Middleware und autoritativen Prüfungen im Data Access Layer (DAL). Das Skill ist plattformunabhängig für Next.js-Projekte konzipiert und kann auch mit Authentifizierungsbibliotheken wie Auth.js oder Clerk kombiniert werden. Es eignet sich besonders für Entwickler, die eigene Log-ins, Registrierungen, Passwort-Wiederherstellungsfunktionen, geschützte Seiten oder API-Clients mit Refresh-Token entwerfen, implementieren oder reviewen.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/rbikbov/frontend-starter/tree/HEAD/.claude/skills/frontend-auth-session

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Authorisierung und Sitzungsverwaltung in Next.js

Dieses Dokument behandelt das Frontend, das eine Sitzung vom Backend erhält (Token oder Sitzungs-ID) und diese speichern, verlängern und prüfen muss. Wenn das Projekt eine Bibliothek oder einen Anbieter verwendet (Auth.js, Better Auth, Clerk usw.), übernimmt diese die Sitzungsmechanik, aber die Regeln der Abschnitte 2, 6–10 bleiben gültig. Der Code befindet sich in references/implementation.md.

Priorität der Regeln. Die Anweisungen des Benutzers und die Agent-Dateien des Projekts sind wichtiger. Wenn das Backend etwas anderes vorgibt (z. B. selbst Cookies setzt), befolge das Backend und dokumentiere die Abweichung in der README oder einem ADR.

Bezeichnungen: MUST – darf nicht verletzt werden; SHOULD – standardmäßig ja, Abweichung erfordert eine Begründung.


1. Auswahl des Modells

ModellWann
BFF: Next-Route-Handler erhalten Token vom Backend und legen sie in HttpOnly-CookieBackend gibt Token im Antwortkörper zurück. Standardoption für Next
Backend setzt selbst HttpOnly-CookieFrontend und API auf derselben Website, Backend unter eigener Kontrolle
Bibliothek oder AnbieterOAuth, MFA, Benutzerverwaltung erforderlich – nicht selbst schreiben
Access im Speicher, Refresh in HttpOnly-CookieSPA ohne BFF
Token in localStorage/sessionStorageVerboten: Jedes XSS entführt die Sitzung

Zweite Entscheidung – was der BFF proxyt:

OptionVorteileNachteile
Nur Login / Refresh / Logout, alle anderen Anfragen gehen direkt mit Cookie zur APIBFF kein Engpass; weniger CodeAPI liest Token aus Cookie und konfiguriert CORS; Cookies müssen die API-Domain erreichen
Alle Anfragen über BFFAPI kennt keine Cookies und CORS; ein UrsprungJede Anfrage ein zusätzlicher Hop; BFF muss skaliert werden

Die Entscheidung wird in einem ADR dokumentiert.


2. Cookie (MUST)

  • HttpOnly für alles, das Zugriff gewährt (Access, Refresh, Sitzungs-ID).
  • Secure in der Produktion. Lokal erlauben Browser Secure auf http://localhost, aber nicht auf anderen http-Hosts.
  • SameSite=Lax standardmäßig. Strict sicherer gegen CSRF, aber Benutzer, die über einen Link aus einer E-Mail oder einer anderen Website kommen, wirken beim ersten Aufruf nicht autorisiert – wähle bewusst und dokumentiere den Grund.
  • Path=/; Lebensdauer aus exp des Tokens oder aus der Backend-Antwort, nicht „nach Gefühl“.
  • SHOULD: Präfix __Host- im Namen (erfordert Secure, Path=/, ohne Domain) – Cookie kann nicht von einer Subdomain überschrieben werden.
  • Cookies werden nur serverseitig gesetzt und gelöscht (Route Handler, Server Action, proxy.ts). Server Components können keine Cookies setzen.
  • Ein aus JS nicht lesbares Cookie ist der einzige Sitzungsträger. Ein Flag wie is_authenticated ohne HttpOnly ist nur als UI-Hinweis zulässig und wird niemals für Zugriffsentscheidungen verwendet.

3. BFF: Route Handler (MUST)

  • Login wird mit einem Schema validiert (zod); fehlerhafter Körper – 400.
  • Mutierende Handler prüfen Origin gegen den Ursprung der Anwendung: SameSite=Lax schützt nicht vor Anfragen von Subdomains derselben Site. Server Actions vergleichen Origin selbst mit Host.
  • Der Client-Kontext wird an das Backend weitergeleitet: X-Forwarded-For, User-Agent – sonst sieht das Rate-Limit des Backends nur die IP des Servers.
  • Backend-Fehler werden in Antworten mit kind und Status (401, 403, 422, 429 mit Retry-After, 503) übersetzt, der Backend-Text gelangt nicht nach außen.
  • Refresh: Wenn das Backend den Refresh-Token abgelehnt hat – Cookie löschen und mit 401 antworten. Wenn das Backend nicht erreichbar ist (Netzwerk, 5xx) – Cookie nicht anfassen, mit 503 antworten: Ein temporärer Ausfall sollte nicht ausloggen.
  • Logout: Token im Backend widerrufen (Fehler loggen, aber nicht anzeigen), Cookie in jedem Fall löschen.

4. Wo die Sitzung geprüft wird (MUST)

OrtWas es tutWas es nicht tut
proxy.ts (Next 16+, früher middleware.ts)Optimistische Prüfung auf Vorhandensein des Cookies: Redirect zum Login und von Login-SeitenGreift nicht auf DB oder API zu; nicht der einzige Schutz
Data Access Layer (verifySession() in server-only-Modul)Prüfung nahe an den Daten: in Serverkomponenten, Route Handlern, Server Actions–
LayoutNichts: Layouts werden bei Navigation nicht neu gerendertPrüft keinen Zugriff
BackendQuelle der Wahrheit: prüft Token bei jeder Anfrage–
  • Jeder Route Handler und jede Server Action ist ein öffentlicher Einstiegspunkt und ruft selbst verifySession() auf.
  • proxy.ts prüft das Refresh-Cookie (oder die Sitzungs-ID): Access könnte abgelaufen sein, die Sitzung aber noch leben.
  • Pfadabgleich – nach Segmenten: /app und /app/…, aber nicht /apple.
  • Mit cacheComponents (standardmäßig in Projekten mit Next 16.4) gelten für das Lesen der Sitzung und das Zwischenspeichern von Benutzerdaten eigene Regeln: Bevor du DAL schreibst, lies node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md.
  • Berechtigungen (Rolle, Besitz) werden anhand geprüfter Daten entschieden: per Signatur (JWKS, jose) oder per Backend-Antwort. Ein ungeprüfter JWT-Payload ist keine Grundlage für Zugriff.
  • Server Components können Cookies nicht aktualisieren. Wenn der Access zum Zeitpunkt des serverseitigen Renderings abgelaufen ist: entweder aktualisiert proxy.ts die Sitzung (es kann Cookie in der Antwort setzen – tue dies nur für Dokumentnavigationen, nicht für Prefetch, und beachte die Rotation von Refresh-Token), oder die Seite rendert einen clientseitigen Ladevorgang und der Refresh erfolgt clientseitig.

5. Client: Refresh ohne Race Conditions (MUST)

  • Erkennung „nicht autorisiert“: HTTP 401, oder in einer GraphQL-Antwort (die 200 ist) mindestens ein Fehler mit extensions.code === "UNAUTHENTICATED" (errors.some, nicht errors[0]). Vergleich mit dem Nachrichtentext – nur wenn das Backend keine Codes liefert, in einer einzigen Funktion, mit Kommentar und Test.
  • Single-Flight: Parallele Anfragen, die 401 erhalten, warten auf einen gemeinsamen Refresh und verwenden sein Ergebnis. Das Promise wird in finally zurückgesetzt.
  • Die ursprüngliche Anfrage wird einmal wiederholt. Ein erneuter 401 – Sitzung verloren, ohne zweiten Refresh.
  • Die Refresh-Anfrage selbst durchläuft nicht den Interceptor (sonst Rekursion).
  • Das Refresh-Ergebnis unterscheidet drei Fälle: ausgestellt (true) → wiederholen; abgelehnt (false) → Sitzung abgelaufen; Netzwerk oder 5xx (Exception) → Fehler network, ohne Ausloggen.
  • Während des Logouts ist der Refresh blockiert: Sonst würde eine Anfrage, die während des Logouts einen 401 erhält, sofort die Sitzung wiederherstellen.
  • Abgelaufene Sitzung: Anfrage-Cache leeren, zur Login-Seite mit callbackUrl der aktuellen Seite umleiten.
  • Der Körper der wiederholten Anfrage muss wiederholbar sein (String, FormData), kein einmaliger ReadableStream.

6. Redirect nach Login (MUST)

  • callbackUrl (next, returnTo) wird mit der Funktion getSafeRedirect(raw, fallback, appOrigin) geprüft: lehnt \ und Steuerzeichen ab (Browser wandeln /\evil.com und /<tab>/evil.com in //evil.com um), parst new URL(raw, appOrigin), vergleicht den Origin und gibt nur den Pfad zurück. Eine Prüfung auf „beginnt mit / und nicht mit //“ ist nicht ausreichend.
  • Ein Testsatz dafür – in references/implementation.md, Abschnitt 7.
  • Nach dem Login müssen Serverkomponenten die neuen Cookies sehen: vollständige Navigation (window.location.assign) oder router.replace + router.refresh().

7. Logout (MUST)

  • Der Server löscht das Cookie (Abschnitt 3).
  • Der Client leert den Anfrage-Cache (queryClient.clear()) und alle clientseitigen Stores mit Benutzerdaten: Der nächste Benutzer im selben Tab darf keine fremden Daten sehen, nicht einmal für einen Moment.
  • Navigation zur Login-Seite ohne Möglichkeit, „zurück“ zum zwischengespeicherten geschützten Bildschirm zu gehen (replace oder vollständige Navigation).
  • Logout-Bestätigung – nach Produktwunsch; Button-Sperre während der Anfrage – zwingend.
  • SHOULD: Andere Tabs erfahren vom Logout (BroadcastChannel oder storage-Event über ein Flag), sonst zeigen sie weiterhin Daten bis zum ersten 401 an.

8. Token in Links (Passwort-Reset, E-Mail-Bestätigung)

  • Das Token wird einmalig aus der URL in einen Zustand gelesen, dann wird die Adresse ohne Token ersetzt (router.replace(pathname)): Es bleibt nicht im Verlauf und gelangt nicht in den Referer.
  • Für diese Seiten – Referrer-Policy: no-referrer und keinerlei Drittanbieter-Skripte (Analytics, Widgets).
  • Das Token wird nicht geloggt. Einmaligkeit und Lebensdauer gewährleistet das Backend; das Frontend zeigt einen verständlichen Bildschirm „Link abgelaufen“ mit der Aktion „Neuen anfordern“.

9. Login- und Wiederherstellungsformulare

  • Passwort-Wiederherstellung antwortet gleich für existierende und nicht existierende E-Mail-Adressen („Wenn ein Konto existiert, wurde eine E-Mail gesendet“), ohne preiszugeben, wer registriert ist. Wenn das Backend dies preisgibt – als Einschränkung dokumentieren.
  • Das Absenden wird während der Anfrage blockiert; erneutes Absenden erzeugt keine zweite Anfrage.
  • 429: Nachricht mit Wartezeit aus Retry-After, falls vorhanden.
  • Felder: type="email" + autoComplete="email"; Passwort – autoComplete="current-password" beim Login und "new-password" bei Registrierung und Änderung; Einfügen wird nicht blockiert.
  • Button „Passwort anzeigen“ – type="button", aria-label und aria-pressed, entzieht dem Feld nicht den Fokus.
  • Fehler „Ungültige E-Mail oder Passwort“ präzisiert nicht, was genau falsch ist.
  • Serverfehler werden Feldern per Code zugeordnet; allgemeiner Fehler – über dem Formular.

10. Daten und Logs

  • Benutzerabhängige Antworten gelangen nicht in allgemeine Caches ("use cache", CDN). QueryClient auf dem Server – neu pro Anfrage.
  • In Logs befinden sich keine Token, Passwörter, E-Mail-Adressen. Maximal – Benutzer-ID und kind des Fehlers.
  • Der Payload des Sitzungstokens ist minimal: ID und Rolle, ohne personenbezogene Daten.

11. Tests

  • Unit: getSafeRedirect (vollständiger Satz aus references); Erkennung „nicht autorisiert“ (401, GraphQL-Codes, leerer Körper, HTML statt JSON).
  • Single-Flight: N parallele Anfragen mit 401 → ein Refresh, N Wiederholungen; Refresh abgelehnt → keine Wiederholung, ein Aufruf von onSessionExpired; Refresh via Netzwerk fehlgeschlagen → Fehler network, ohne Logout; erneuter 401 nach erfolgreichem Refresh → ohne zweiten Refresh; während des Logouts → kein Refresh.
  • Route Handler: Cookie-Flags; Refresh abgelehnt → Cookie gelöscht; Backend nicht erreichbar → Cookie vorhanden, 503; ungültiger Körper → 400; fremder Origin → 403.
  • e2e: Login → geschützte Seite → Logout → geschützte Seite nicht erreichbar; Ablauf des Access (kurzer TTL in der Testumgebung) → transparenter Refresh; Passwort-Reset-Link → Token aus der Adressleiste verschwunden.

12. Definition of Done

  • Kein Token wird aus JS gelesen; localStorage wird nicht für die Sitzung verwendet.
  • Cookie: HttpOnly, Secure in der Produktion, SameSite, Path, Lebensdauer aus Token.
  • Refresh: Ablehnung und Nichterreichbarkeit des Backends werden unterschieden.
  • Single-Flight mit Ergebnis, eine Wiederholung, Blockade während Logout – durch Tests abgedeckt.
  • proxy.ts – nur optimistische Prüfung; DAL wird in Handlern, Actions und Serverkomponenten aufgerufen.
  • getSafeRedirect mit vollständigem Testsatz.
  • Logout leert Cache und Client-Stores.
  • Token aus Links werden aus der Adressleiste entfernt.
  • In Logs keine Token und personenbezogenen Daten.
  • Modell- und Proxy-Auswahl im ADR dokumentiert.

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

rbikbov/frontend-starter

Creates Architecture Decision Records documenting key technical decisions with context, alternatives considered, tradeoffs, consequences, and decision owners. Use when documenting "architecture decisions", "technical choices", "design decisions", or "ADRs".

rbikbov/frontend-starter

Проверка фронтенд-кода и документации на «нейрослоп» перед коммитом, PR или публикацией репозитория: остатки генераторов, мёртвые и недоделанные опции, утверждения в README и комментариях, не подтверждённые кодом, маркетинговый тон и эмодзи, лишние абстракции, хрупкая обработка ошибок, персональные данные в логах, несуществующие классы стилей. Использовать, когда просят проверить код или репозиторий «на слоп», подготовить проект к публикации или показу, сделать ревью кода, написанного с помощью ИИ, или перед тем как закоммитить собственную большую правку.

rbikbov/frontend-starter

Процесс разработки фичи с агентом: PRD с Non-Goals → компактная спецификация → задачи по одной на коммит → read-only исследование → tracer bullet → сборка с тестом сначала → независимое ревью субагентом → проверка в браузере. Использовать, когда просят сделать фичу, экран, пользовательский сценарий или заметное изменение поведения; когда нужно написать PRD, спецификацию или разбить работу на задачи; когда работа больше одного коммита. Не использовать для опечаток и однострочных правок.

rbikbov/frontend-starter

Старт нового фронтенд-проекта на Next.js (App Router) + TypeScript: фундамент, который закладывается до первой фичи. Использовать, когда нужно: создать проект с нуля; решить, нужны ли Next.js, монорепо, BFF, микрофронтенды; разложить FSD поверх роутинга Next; завести строгий TS, линтер границ, env-конфиг, модель ошибок и API-клиент, тесты, git-хуки, CI, каркас README; навести порядок в молодом проекте после генератора.

Verwandte Skills