
Sichere Laravel Sanctum Next.js Authentifizierung bauen

Die Einrichtung einer sicheren Laravel Sanctum Next.js Authentifizierung gilt oft als der absolute Endgegner in der modernen Webentwicklung. Wenn man das Backend und das Frontend architektonisch komplett voneinander entkoppelt, verliert man den Luxus der traditionellen, nahtlosen Laravel-Sessions. Plötzlich starrt man auf grellrote CORS-Fehler, verlorene Tokens und hartnäckige "401 Unauthorized"-Meldungen in der Browser-Konsole. Nächte voller Frustration beim API-Debugging sind hier für viele Entwickler fast schon ein Initiationsritus.
In der Vergangenheit wurde dieses Problem oft mit einem vermeintlich einfachen Hack gelöst: Man generierte JWT-Tokens (JSON Web Tokens) und legte diese einfach im LocalStorage des Browsers ab. Für ein Enterprise Headless CMS ist das jedoch ein absolutes No-Go. Ein einziges bösartiges JavaScript-Snippet (eine sogenannte XSS-Attacke), das sich über ein kompromittiertes NPM-Paket oder ein unsicheres Kommentarfeld einschleicht, reicht aus, um diese Tokens im Klartext auszulesen und Administratoren-Konten vollständig zu kapern.
Wir bauen hier eine Festung. Wir nutzen das SPA-Authentifizierungs-Feature (Single Page Application) von Laravel, um unsichtbare, absolut sichere HttpOnly-Cookies auszutauschen.
1. Das Konzept: Warum HttpOnly-Cookies die Festung sichern
Bevor wir Code schreiben, müssen wir die Architektur hinter einer Laravel Sanctum Next.js Verbindung verstehen. Bei der SPA-Authentifizierung schickt unser Next.js Frontend die Login-Daten (E-Mail und Passwort) an Laravel. Wenn diese korrekt sind, antwortet Laravel nicht mit einem nackten Text-Token im JSON-Body.
Stattdessen weist der Server den Browser des Nutzers über die HTTP-Header an, zwei spezielle Session-Cookies (laravel_session und XSRF-TOKEN) zu speichern.
Der entscheidende Clou dabei: Das Session-Cookie wird vom Backend mit dem strikten Flag HttpOnly versehen. Das bedeutet, dass kein einziges clientseitiges JavaScript-Skript der Welt – nicht einmal dein eigener Next.js-React-Code – dieses Cookie auslesen oder manipulieren kann. Der Browser des Nutzers verwaltet es streng geheim in einem Tresor und hängt es ab sofort völlig automatisch an jede zukünftige Axios- oder Fetch-Anfrage an unsere API an.
XSS-Angriffe, die versuchen, Tokens zu stehlen, laufen somit komplett ins Leere, da der Angreifer schlichtweg nicht an das Cookie herankommt. Laravel erkennt bei jedem einkommenden Request das Cookie, ordnet es der richtigen Session in der Datenbank (oder in Redis) zu und weiß sofort, welcher Redakteur gerade vor dem Bildschirm sitzt.
Das ist die sicherste Methode, um eine entkoppelte Architektur zu betreiben – vorausgesetzt, man konfiguriert die CORS-Richtlinien und den CSRF-Handshake richtig.

2. Die Festung vorbereiten: CORS und Sanctum in Laravel konfigurieren
Damit dieses magische Cookie-Ping-Pong funktioniert, müssen beide Systeme absolut sicher wissen, dass sie sich vertrauen können. Da dein Laravel-Backend (z. B. auf localhost:8000) und dein Next.js-Frontend (auf localhost:3000) auf unterschiedlichen Ports laufen, blockiert der Browser standardmäßig jeden Cookie-Austausch aus strengen Sicherheitsgründen. Diesen unsichtbaren Türsteher nennen wir CORS (Cross-Origin Resource Sharing).
Um diese Hürde für unsere Laravel Sanctum Next.js Authentifizierung zu nehmen, müssen wir Laravel exakt mitteilen, wer legitimen Zugriff hat.
Öffne dein Laravel-Backend (cms-backend) und navigiere in die .env-Datei. Hier definieren wir unsere "Stateful Domains" – also die exakten Adressen, von denen Laravel Session-Cookies überhaupt erst akzeptiert und verarbeitet.
Füge diese Zeilen in deine .env ein (oder passe sie an, falls sie schon existieren):
1# 1. Die Domain unseres Next.js Frontends (Wichtig: Ohne http:// oder https://!)
2SANCTUM_STATEFUL_DOMAINS=localhost:3000
3
4# 2. Die Base-Domain für das Session-Cookie (bei localhost oft leer oder .localhost)
5# In der Produktion wäre das z.B. .deincms.com (mit führendem Punkt für Subdomains)
6SESSION_DOMAIN=localhost
7
8# 3. Erlaube unserem Frontend, generelle API-Anfragen zu stellen (Hier MIT http://)
9CORS_ALLOWED_ORIGINS=http://localhost:3000Ein kleiner, aber fataler Fehler, der an dieser Stelle oft gemacht wird: Achte penibel darauf, dass bei SANCTUM_STATEFUL_DOMAINS kein http:// steht, bei CORS_ALLOWED_ORIGINS hingegen schon. Wenn du das vertauschst, wirst du später im Frontend unweigerlich mit "419 Page Expired"-Fehlern überhäuft.
Die Sanctum-Middleware in Laravel 12 aktivieren
Als Nächstes müssen wir Laravel anweisen, einkommende API-Requests durch die Sanctum-Middleware zu leiten. Nur so kann das Framework bei jedem Aufruf prüfen, ob ein gültiges Session-Cookie an der Anfrage hängt.
In älteren Laravel-Versionen passierte das noch umständlich im Http/Kernel.php. In Laravel 12 haben wir eine viel schlankere Architektur. Öffne die Datei bootstrap/app.php.
Hier aktivieren wir die zustandsbehaftete (stateful) API-Authentifizierung mit einer einzigen, eleganten Methode:
1<?php
2
3use Illuminate\Foundation\Application;
4use Illuminate\Foundation\Configuration\Exceptions;
5use Illuminate\Foundation\Configuration\Middleware;
6
7return Application::configure(basePath: dirname(__DIR__))
8 ->withRouting(
9 web: __DIR__.'/../routes/web.php',
10 api: __DIR__.'/../routes/api.php',
11 commands: __DIR__.'/../routes/console.php',
12 health: '/up',
13 )
14 ->withMiddleware(function (Middleware $middleware) {
15
16 // Diese Zeile ist der absolute Schlüssel für unsere SPA-Auth!
17 // Sie aktiviert die Session-Cookies für alle Routen in routes/api.php
18 $middleware->statefulApi();
19
20 })
21 ->withExceptions(function (Exceptions $exceptions) {
22 //
23 })->create();Pro-Tipp: Laravel cached Konfigurationsdateien extrem aggressiv. Wenn du Änderungen an der .env vornimmst, führe in deinem Terminal immer sofort php artisan optimize:clear aus. Das erspart dir stundenlanges Debugging von "Geister-Fehlern", die eigentlich gar nicht mehr existieren.
Unsere Backend-Infrastruktur weiß nun ganz genau, mit wem sie kommunizieren darf und wie sie einkommende Cookies verarbeiten muss.

3. Der AuthController: Den Login-Prozess im Backend bauen
Laravel weiß nun, wem es vertrauen darf. Um unsere Laravel Sanctum Next.js Architektur zum Leben zu erwecken, brauchen wir jetzt die tatsächlichen Endpunkte für den Login, den Logout und den Abruf der aktuellen Nutzerdaten. Wir verzichten auf die vorgefertigten (oft zu überladenen) Starter-Kits wie Breeze und bauen uns einen maßgeschneiderten, extrem schlanken API-Controller.
Führe in deinem Backend-Terminal folgenden Befehl aus:
php artisan make:controller Api/AuthControllerÖffne den neu generierten app/Http/Controllers/Api/AuthController.php. Hier bauen wir nun die Logik ein, um den Nutzer zu authentifizieren und die Session extrem sicher zu starten:
1<?php
2
3namespace App\Http\Controllers\Api;
4
5use App\Http\Controllers\Controller;
6use Illuminate\Http\Request;
7use Illuminate\Support\Facades\Auth;
8
9class AuthController extends Controller
10{
11 public function login(Request $request)
12 {
13 // 1. Validierung der einkommenden Daten
14 $credentials = $request->validate([
15 'email' => ['required', 'email'],
16 'password' => ['required'],
17 ]);
18
19 // 2. Prüfen, ob die Daten in der Datenbank existieren
20 if (Auth::attempt($credentials)) {
21
22 // WICHTIG: Session neu generieren!
23 // Das verhindert gefährliche Session-Fixation-Angriffe.
24 $request->session()->regenerate();
25
26 // 3. Erfolgsantwort senden (Laravel hängt das Cookie automatisch an!)
27 return response()->json([
28 'user' => Auth::user(),
29 'message' => 'Login erfolgreich'
30 ]);
31 }
32
33 // Falls die Zugangsdaten falsch sind
34 return response()->json([
35 'message' => 'Die angegebenen Zugangsdaten sind falsch.'
36 ], 401);
37 }
38
39 public function logout(Request $request)
40 {
41 // Den Web-Guard ausloggen
42 Auth::guard('web')->logout();
43
44 // Session komplett zerstören und Cookies invalidieren
45 $request->session()->invalidate();
46 $request->session()->regenerateToken();
47
48 // 204 No Content ist der REST-Standard für erfolgreiches Löschen/Logout
49 return response()->noContent();
50 }
51
52 public function user(Request $request)
53 {
54 // Gibt den aktuell eingeloggten Nutzer zurück
55 return response()->json($request->user());
56 }
57}Ein essenzielles Detail in diesem Code ist die Zeile $request->session()->regenerate();. Ohne diese Zeile wäre deine Anwendung anfällig für "Session Fixation". Ein Angreifer könnte dir eine präparierte Session-ID unterschieben, bevor du dich einloggst, und danach deine Rechte übernehmen. Laravel macht es uns hier unfassbar einfach, Enterprise-Sicherheitsstandards einzuhalten.
Die Routen absichern
Jetzt müssen wir diese Methoden nur noch mit unserer API verknüpfen. Öffne die Datei routes/api.php und registriere die drei neuen Endpunkte.
Beachte dabei: Die /login-Route muss für jeden (auch Gäste) offen sein. Die Routen /user und /logout hingegen müssen zwingend durch den unsichtbaren Türsteher von Sanctum geschützt werden:
1<?php
2
3use Illuminate\Support\Facades\Route;
4use App\Http\Controllers\Api\PostController;
5use App\Http\Controllers\Api\AuthController;
6
7// Öffentliche Route für den Login
8Route::post('/login', [AuthController::class, 'login']);
9
10// Unsere bestehende Post-Route (falls du sie öffentlich lassen willst)
11Route::apiResource('posts', PostController::class);
12
13// Nur eingeloggte Nutzer mit einem validen Cookie dürfen hier rein
14Route::middleware('auth:sanctum')->group(function () {
15 Route::post('/logout', [AuthController::class, 'logout']);
16 Route::get('/user', [AuthController::class, 'user']);
17
18 // Später verschieben wir auch die POST/PUT/DELETE Routen für Artikel hierhin!
19});Unser Backend-Tresor ist nun vollständig verriegelt und abmarschbereit. Wenn das Frontend jetzt korrekte Login-Daten schickt, verifiziert Laravel diese, generiert eine neue Session und sendet völlig unsichtbar im Hintergrund das magische HttpOnly-Cookie an den Browser zurück.
Wir haben das Backend gemeistert. Jetzt wechseln wir die Seiten.

4. Das Next.js Frontend: Der CSRF-Handshake und Axios konfigurieren
Damit unsere Laravel Sanctum Next.js Architektur sicher funktioniert, dürfen wir nicht einfach blind Login-Daten an die API feuern. Wir müssen Laravel vor dem eigentlichen Login beweisen, dass unser Frontend legitim ist und der Request nicht von einem bösartigen Drittanbieter-Skript (Cross-Site Request Forgery) stammt.
Dieser Beweis geschieht über den sogenannten CSRF-Handshake. Bevor wir den echten Login-Request (/api/login) absenden, klopfen wir einmal kurz und ohne Daten bei Laravel an (/sanctum/csrf-cookie), um uns einen frischen, kryptografischen CSRF-Token abzuholen.
Um das im Next.js-Frontend sauber und wartbar zu lösen, konfigurieren wir uns eine zentrale Axios-Instanz. Das erspart uns die Qual, bei jedem einzelnen Fetch-Call die Header und Cookie-Einstellungen manuell mitgeben zu müssen.
Wechsle in dein Next.js Projekt (cms-frontend) und installiere Axios:
npm install axiosErstelle nun eine neue Datei unter src/lib/axios.ts und konfiguriere unseren maßgeschneiderten API-Client:
1import Axios from 'axios';
2
3const axios = Axios.create({
4 // Zeigt auf dein lokales Laravel Backend (ohne abschließenden Slash!)
5 baseURL: process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000',
6 headers: {
7 'X-Requested-With': 'XMLHttpRequest',
8 'Accept': 'application/json',
9 },
10 // DAS IST DIE WICHTIGSTE ZEILE IM GESAMTEN FRONTEND!
11 // Sie zwingt den Browser, das HttpOnly-Cookie bei jeder Anfrage mitzusenden.
12 withCredentials: true,
13});
14
15export default axios;Die Login-Seite: Den Handshake ausführen
Jetzt können wir in einer beliebigen Client-Component (z. B. unserer neuen Login-Seite unter src/app/login/page.tsx) den magischen Zweischritt-Login durchführen.
Erstelle die Datei und baue dieses funktionale Formular ein:
1'use client';
2
3import { useState } from 'react';
4import axios from '@/lib/axios';
5import { useRouter } from 'next/navigation';
6
7export default function LoginPage() {
8 const [email, setEmail] = useState('');
9 const [password, setPassword] = useState('');
10 const router = useRouter();
11
12 const handleLogin = async (e: React.FormEvent) => {
13 e.preventDefault();
14
15 try {
16 // Schritt 1: Den CSRF-Handshake durchführen (holt das Sicherheits-Cookie)
17 await axios.get('/sanctum/csrf-cookie');
18
19 // Schritt 2: Den eigentlichen Login absenden
20 const response = await axios.post('/api/login', { email, password });
21
22 console.log('Login erfolgreich!', response.data.user);
23
24 // Nach erfolgreichem Login leiten wir ins (noch zu bauende) Dashboard weiter
25 router.push('/admin/dashboard');
26
27 } catch (error) {
28 console.error('Login fehlgeschlagen. Stimmen die Daten?', error);
29 }
30 };
31
32 return (
33 <div className="min-h-screen flex items-center justify-center bg-zinc-950">
34 <form onSubmit={handleLogin} className="w-full max-w-md p-8 bg-zinc-900 border border-zinc-800 rounded-2xl shadow-xl">
35 <h1 className="text-2xl font-bold text-zinc-100 mb-6">CMS Login</h1>
36
37 <input
38 type="email"
39 placeholder="E-Mail Adresse"
40 value={email}
41 onChange={(e) => setEmail(e.target.value)}
42 className="w-full mb-4 p-3 bg-zinc-950 border border-zinc-800 rounded-lg text-white"
43 />
44
45 <input
46 type="password"
47 placeholder="Passwort"
48 value={password}
49 onChange={(e) => setPassword(e.target.value)}
50 className="w-full mb-6 p-3 bg-zinc-950 border border-zinc-800 rounded-lg text-white"
51 />
52
53 <button type="submit" className="w-full bg-emerald-500 hover:bg-emerald-600 text-white font-semibold p-3 rounded-lg transition-colors">
54 Sicher Einloggen
55 </button>
56 </form>
57 </div>
58 );
59}Wenn du deine Next.js-App jetzt startest, deine korrekten Daten eingibst und auf "Einloggen" klickst, passiert die Magie unsichtbar im Hintergrund. Öffne nach dem Login die DevTools deines Browsers (Reiter "Application" -> "Cookies"). Du wirst sehen, dass Laravel dir die laravel_session und XSRF-TOKEN Cookies dort sicher und unangetastet hinterlegt hat.
Client-seitige Anfragen mit unserer axios-Instanz funktionieren ab jetzt vollautomatisch!

5. Das Server Component Problem: Cookies im App Router weiterleiten
Hier scheitern 90 % aller Entwickler, die versuchen, eine sichere Laravel Sanctum Next.js Verbindung mit dem modernen App Router (RSC) aufzubauen. Wenn du diesen Abschnitt verstehst, hast du die Headless-Architektur gemeistert.
Wenn du in deinem Frontend auf einen Button klickst, führt der Browser den JavaScript-Code aus und sendet die Cookies völlig automatisch an Laravel. Alles ist gut. Aber was passiert, wenn du eine Server Component (page.tsx ohne den 'use client' Zusatz) hast, die geschützte Daten aus dem Laravel-Backend fetchen soll, bevor die Seite überhaupt an den Nutzer ausgeliefert wird?
In diesem Moment macht der Next.js Node-Server die Anfrage an Laravel, nicht dein Browser! Der Node-Server hat standardmäßig keine Ahnung von deinen Cookies. Er leitet sie nicht automatisch weiter. Laravel sieht eine nackte, anonyme Anfrage und blockt sie knallhart mit einem 401 Unauthorized ab.
Wir müssen Next.js also explizit anweisen: "Nimm die Cookies, die der Browser des Nutzers gerade an dich geschickt hat, und hänge sie manuell an den Fetch-Request in Richtung Laravel an."
Der serverFetch Helfer
Hier ist das perfekte Pattern für Daten-Fetches in Server Components. Erstelle eine neue Helfer-Funktion unter src/lib/server-fetch.ts:
1import { cookies } from 'next/headers';
2
3export async function serverFetch(endpoint: string, options: RequestInit = {}) {
4 // 1. Hole alle Cookies, die der Browser des Nutzers an unseren Next.js Server gesendet hat
5 const cookieStore = await cookies();
6 const cookieString = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ');
7
8 // 2. Erstelle die Standard-Header für Laravel
9 const headers = new Headers(options.headers);
10 headers.set('Accept', 'application/json');
11
12 // 3. WICHTIG: Hänge die extrahierten Cookies manuell an den Request in Richtung Laravel an!
13 if (cookieString) {
14 headers.set('Cookie', cookieString);
15 }
16
17 // 4. Sende die native fetch-Anfrage an unser Backend
18 const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL}${endpoint}`, {
19 ...options,
20 headers,
21 });
22
23 return response;
24}Warum nutzen wir hier fetch und nicht unsere vorherige axios-Instanz? Next.js hat die native, webstandardisierte fetch-API extrem stark erweitert. Nur mit fetch kannst du die grandiosen Next.js Caching- und Revalidierungs-Features auf dem Server nutzen. Axios unterstützt diese im App Router nicht nativ.
Die Server Component in Aktion
Jetzt kannst du in jeder beliebigen Server Component völlig sicher, rasend schnell und zu 100 % SEO-freundlich auf geschützte Laravel-Routen zugreifen:
1// src/app/admin/dashboard/page.tsx
2import { serverFetch } from '@/lib/server-fetch';
3import { redirect } from 'next/navigation';
4
5export default async function DashboardPage() {
6 // Nutze unseren neuen Helfer, der die Cookies automatisch weiterleitet
7 const response = await serverFetch('/api/user');
8
9 // Wenn Laravel uns nicht kennt (kein valides Cookie), ab zum Login!
10 if (!response.ok) {
11 redirect('/login');
12 }
13
14 // Die Daten sicher auslesen
15 const user = await response.json();
16
17 return (
18 <div className="p-8 text-zinc-100 bg-zinc-950 min-h-screen">
19 <h1 className="text-3xl font-bold mb-4">Willkommen im Tresor, {user.name}!</h1>
20 <p className="text-zinc-400">
21 Diese Seite wurde komplett auf dem Server gerendert. Keine Lade-Spinner,
22 absolute Sicherheit durch unsere Laravel Sanctum Next.js Architektur.
23 </p>
24 </div>
25 );
26}Boom! Du hast den Endgegner besiegt. Dein Frontend kann sich nun sowohl clientseitig (über Axios) als auch serverseitig (über unseren nativen Fetch-Wrapper) sicher mit dem Backend unterhalten, ohne jemals angreifbare Tokens im LocalStorage ablegen zu müssen.

Zusammenfassung: Authentifizierung auf Enterprise-Niveau
Der Bau einer wasserdichten Laravel Sanctum Next.js Authentifizierung erfordert ein tiefes Verständnis dafür, wie Cookies, CORS-Richtlinien und moderne Server Components interagieren. Du hast gelernt, warum JWTs im LocalStorage ein massives Sicherheitsrisiko darstellen und wie Laravel uns mit unsichtbaren HttpOnly-Cookies auf Enterprise-Niveau absichert.
Wir haben den Backend-Tresor über die korrekte Umgebungskonfiguration (.env) und die Sanctum-Middleware für unser Frontend geöffnet. Im Frontend haben wir den lebenswichtigen CSRF-Handshake über eine angepasste Axios-Instanz implementiert. Und – das ist der absolute Meilenstein für den modernen App Router – wir haben Next.js beigebracht, wie es die sensiblen Session-Cookies in React Server Components abfängt und sicher an die API weiterleitet, um echtes Server-Side Rendering (SSR) für geschützte Routen zu ermöglichen. Dein System ist nun eine Festung.
Teil der Serie
Headless CMS mit Laravel und Next.js
Headless CMS mit Laravel 12 & Next.js: Der ultimative Guide Pillar
Das perfekte Laravel Next.js Setup: Projektstruktur für dein Headless CMS
Datenbank-Design für ein skalierbares Headless CMS
Perfekte RESTful API mit Laravel 12 & API Resources bauen
Sichere Laravel Sanctum Next.js Authentifizierung bauen
Häufig gestellte Fragen (FAQ)
Ausblick auf Teil 5: Das ultimative Admin-Dashboard mit shadcn/ui
Unsere Architektur steht, die Daten fließen blitzschnell, und das System ist absolut sicher verriegelt. Es ist Zeit, dass unser Headless CMS endlich ein Gesicht bekommt!
Im nächsten Teil unserer Masterclass verlassen wir den dunklen Maschinenraum des Backends und widmen uns voll und ganz dem Next.js Frontend. Wir werden ein hochmodernes, responsives Redaktions-Dashboard bauen. Dabei verzichten wir auf aufgeblähte CSS-Frameworks der Vergangenheit und nutzen stattdessen Tailwind CSS in Kombination mit den fantastischen Komponenten von shadcn/ui. Wir konstruieren atemberaubende Datentabellen, modale Fenster und interaktive Formulare, die sich anfühlen wie eine native App und nicht wie eine klobige Webseite.
Hier geht es zu Teil 5: Das ultimative Admin-Dashboard mit Next.js & shadcn/ui bauen

Dietrich Bojko
Senior Webentwickler
Webinteger arbeitet seit vielen Jahren produktiv mit
Linux-basierten Entwicklungsumgebungen unter Windows.
Der Fokus liegt auf
performanten Setups mit WSL 2, Docker, PHP, Node.js und modernen
Build-Tools in realen Projekten –
nicht auf theoretischen Beispielkonfigurationen.
Die Artikel dieser Serie entstehen direkt aus dem täglichen Einsatz in Kunden- und Eigenprojekten und dokumentieren bewusst auch typische Fehler, Engpässe und bewährte Workarounds.


