Route Guards, Tokens und rollenbasierte Zugriffskontrolle
Nuxt Middleware ist der zentrale Mechanismus, um Routen zu schützen, Authentifizierung zu erzwingen und rollenbasierte Zugriffskontrolle sauber in der App-Schicht zu implementieren – ohne Sicherheitslogik in jede einzelne Seite zu kopieren.
Inhaltsverzeichnis
- 1. Warum Nuxt Middleware für Auth?
- 2. Middleware-Typen: inline, named und global
- 3. Auth-Composable und Token-Verwaltung
- 4. Route Guard mit defineNuxtRouteMiddleware
- 5. Token-Refresh und stille Erneuerung
- 6. Rollenbasierte Zugriffskontrolle (RBAC)
- 7. Server-seitige Middleware und API-Schutz
- 8. Typische Fehler und wie man sie vermeidet
- 9. Middleware-Strategien im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Nuxt Middleware für Auth?
Authentifizierung in einer Nuxt-Applikation lässt sich auf viele Arten implementieren – direkt in der setup-Funktion einer Seite, als Plugin oder als dedizierte Nuxt Middleware. Der entscheidende Unterschied liegt darin, wo die Prüfung stattfindet: Eine Middleware läuft vor dem Rendern der Zielseite und kann die Navigation abbrechen oder umleiten, ohne dass der Nutzer auch nur einen kurzen Blick auf geschützte Inhalte bekommt. Seiten-interne Prüfungen reagieren hingegen erst, nachdem die Komponente bereits initialisiert wurde – ein konzeptionelles Sicherheitsleck, das im besten Fall zu einem kurzen Flackern führt, im schlimmsten Fall zu einer Race Condition mit sichtbaren Daten.
Die Nuxt Middleware-Schicht ist außerdem der einzige Ort, an dem du Zugriffskontrolle zentral und konsistent durchsetzen kannst. Wenn das Berechtigungsmodell sich ändert – zum Beispiel eine neue Rolle wird eingeführt – musst du nur die Middleware anpassen, nicht jede einzelne Seite. Das reduziert den Wartungsaufwand drastisch und eliminiert eine häufige Fehlerquelle: vergessene Guards auf neuen Routen. In Kombination mit Pinias Store-Architektur für den Auth-Zustand entsteht eine klare Trennung zwischen Zustandsverwaltung, Zugriffslogik und Darstellung – das ist das Fundament produktionsreifer Nuxt Middleware für Auth.
2. Middleware-Typen: inline, named und global
Nuxt 3 kennt drei Arten von Middleware, die sich in ihrer Reichweite und Konfiguration unterscheiden. Inline-Middleware wird direkt in der definePageMeta-Funktion einer Seite definiert und gilt ausschließlich für diese eine Route. Sie eignet sich für Sonderfälle, bei denen eine Seite spezifische Prüfungen benötigt, die nirgendwo sonst relevant sind. Der Nachteil: Logik verteilt sich auf viele Seiten, was Wartung erschwert.
Named Middleware wird als separate Datei unter middleware/auth.ts abgelegt und per definePageMeta({ middleware: ['auth'] }) auf Seiten angewendet. Das ist das empfohlene Muster für Nuxt Middleware im Auth-Kontext: Die Logik liegt an einem einzigen Ort, lässt sich testen und wird nur auf Seiten aktiviert, die explizit darauf verweisen. Globale Middleware mit dem Suffix .global.ts läuft bei jedem Routenwechsel automatisch – sie eignet sich für übergreifende Dinge wie Analytics-Tracking oder universelle Session-Checks, nicht aber für granulare Zugriffskontrollen, weil dann jede öffentliche Seite die Guard-Logik durchlaufen würde, was unnötigen Overhead erzeugt.
// middleware/auth.ts — Named route middleware for protected areas
import { useAuthStore } from '~/stores/auth'
export default defineNuxtRouteMiddleware((to, from) => {
const auth = useAuthStore()
// Redirect to login if no valid token is present
if (!auth.isAuthenticated) {
return navigateTo({
path: '/login',
query: { redirect: to.fullPath }, // preserve intended destination
})
}
})
Die Datei liegt unter middleware/auth.ts und wird auf geschützten Seiten über definePageMeta({ middleware: ['auth'] }) aktiviert. Der Rückgabewert von navigateTo beendet die Navigation zur ursprünglichen Zielroute und leitet stattdessen zum Login weiter, wobei der ursprünglich gewünschte Pfad als Query-Parameter mitgegeben wird. Nach erfolgreichem Login kann die App den Nutzer dann direkt an das ursprüngliche Ziel weiterleiten – ein wichtiges UX-Detail, das viele Implementierungen vergessen.
3. Auth-Composable und Token-Verwaltung
Bevor die Nuxt Middleware sinnvoll arbeiten kann, braucht sie eine zuverlässige Quelle für den Auth-Zustand. Das gelingt am besten mit einem dedizierten Pinia-Store, der Token, Nutzerdaten und den Authentifizierungsstatus zentral verwaltet. Ein useAuthStore kapselt alle Token-Operationen: Speichern nach Login, Laden beim App-Start und Löschen beim Logout. Tokens werden in einem HTTP-only Cookie gespeichert – niemals im localStorage, wo XSS-Angriffe direkten Zugriff hätten.
Das Composable useAuth abstrahiert die Interaktion mit dem Store und der API: Es enthält die login-Funktion, die Credentials an den Backend-Endpunkt sendet, das Token setzt und den Nutzer in den Store lädt. Die logout-Funktion löscht Token und Store-Zustand und leitet auf die Login-Seite um. Durch diese Abstraktion bleibt die Nuxt Middleware schlank – sie fragt nur auth.isAuthenticated ab und überlässt die komplexe Token-Logik dem Composable. Das folgt dem Single-Responsibility-Prinzip und macht Unit-Tests für beide Schichten deutlich einfacher.
4. Route Guard mit defineNuxtRouteMiddleware
Die Funktion defineNuxtRouteMiddleware ist der offizielle Nuxt-3-Wrapper für Middleware-Funktionen. Sie gibt der Nuxt-Runtime das notwendige Kontext-Objekt und stellt sicher, dass Composables wie useAuthStore korrekt im Nuxt-Kontext ausgeführt werden – was außerhalb von defineNuxtRouteMiddleware nicht garantiert ist. Der Guard erhält to und from als Routenobjekte und kann entweder undefined (Navigation fortsetzen), navigateTo('/pfad') (Umleitung) oder abortNavigation() (Navigation abbrechen) zurückgeben.
Ein vollständiger Route Guard für eine geschützte Seite prüft nicht nur, ob ein Token vorhanden ist, sondern auch ob es noch gültig ist. Dazu ruft die Nuxt Middleware eine Hilfsfunktion auf, die den JWT dekodiert und den exp-Claim gegen die aktuelle Uhrzeit prüft. Ist das Token abgelaufen, wird nicht sofort zur Login-Seite weitergeleitet, sondern zuerst ein Refresh versucht. Erst wenn der Refresh fehlschlägt, erfolgt die Umleitung. Dieses Muster vermeidet unnötige Login-Aufforderungen für Nutzer, deren Refresh-Token noch gültig ist.
// middleware/auth.ts — Full guard with token expiry check and refresh attempt
import { useAuthStore } from '~/stores/auth'
import { isTokenExpired, decodeJwt } from '~/utils/jwt'
export default defineNuxtRouteMiddleware(async (to) => {
const auth = useAuthStore()
// Allow unauthenticated access to public routes
if (to.meta.public) return
// No token at all → redirect to login
if (!auth.accessToken) {
return navigateTo({ path: '/login', query: { redirect: to.fullPath } })
}
// Token expired → attempt silent refresh before redirecting
if (isTokenExpired(auth.accessToken)) {
const refreshed = await auth.refreshAccessToken()
if (!refreshed) {
auth.clearSession()
return navigateTo({ path: '/login', query: { redirect: to.fullPath } })
}
}
// Token valid: navigation proceeds
})
5. Token-Refresh und stille Erneuerung
Die stille Token-Erneuerung ist eines der komplexesten Themen in der Auth-Implementierung mit Nuxt Middleware. Das Prinzip: Ein kurzlebiges Access-Token (z.B. 15 Minuten) wird automatisch durch ein langlebiges Refresh-Token (z.B. 7 Tage) erneuert, ohne dass der Nutzer etwas davon merkt. Der Refresh-Endpunkt akzeptiert das Refresh-Token – typischerweise als HTTP-only-Cookie – und gibt ein neues Access-Token zurück. Das Access-Token wiederum wird im Pinia-Store gehalten, nicht persistiert, damit es nach einem Browser-Neustart automatisch erneuert wird.
Das kritische Problem bei der Refresh-Logik: Mehrere gleichzeitige Requests können gleichzeitig ein abgelaufenes Token entdecken und parallel Refresh-Requests auslösen. Das führt zu Race Conditions: Das erste Refresh macht das Refresh-Token ungültig, alle anderen schlagen fehl und leiten den Nutzer zum Login weiter – obwohl das erste Refresh erfolgreich war. Die Lösung ist ein Singleton-Promise im Auth-Store: Sobald ein Refresh gestartet wird, wird das Promise gespeichert. Alle weiteren Requests, die ebenfalls refreshen wollen, hängen sich an dasselbe Promise und warten auf das Ergebnis statt einen neuen Request zu starten.
6. Rollenbasierte Zugriffskontrolle (RBAC)
Wenn eine Applikation mehrere Nutzerrollen hat – etwa admin, editor und viewer – reicht eine einfache Authentifizierungsprüfung nicht aus. Nuxt Middleware für RBAC prüft nicht nur das Vorhandensein eines Tokens, sondern auch ob der im Token enthaltene Role-Claim die für die Zielroute erforderliche Berechtigung enthält. Die erlaubten Rollen pro Route werden am elegantesten über definePageMeta als Meta-Daten definiert: definePageMeta({ middleware: ['auth'], roles: ['admin', 'editor'] }). Die Middleware liest to.meta.roles aus und vergleicht mit der Rolle aus dem Auth-Store.
Wichtig: Die RBAC-Prüfung in der Middleware ist ausschließlich ein UI-Schutz. Sie verhindert, dass Nutzer ohne Berechtigung die falsche Seite sehen, kann aber keine serverseitige Durchsetzung ersetzen. Jeder API-Endpunkt muss den Rollen-Claim aus dem JWT selbst prüfen, unabhängig davon, was die Nuxt Middleware client-seitig entschieden hat. Die Middleware-Prüfung und die API-Prüfung sind zwei unabhängige Sicherheitsebenen – beide sind notwendig, keine von beiden ist allein ausreichend.
// middleware/role-guard.ts — RBAC middleware using route meta
import { useAuthStore } from '~/stores/auth'
export default defineNuxtRouteMiddleware((to) => {
const auth = useAuthStore()
const requiredRoles = to.meta.roles as string[] | undefined
// No role restriction defined → access granted
if (!requiredRoles || requiredRoles.length === 0) return
// Check if user holds at least one of the required roles
const hasRole = requiredRoles.some(role => auth.user?.roles?.includes(role))
if (!hasRole) {
// Redirect to 403 page or dashboard — never expose route existence
return navigateTo('/403')
}
})
// Usage in a page component:
// definePageMeta({ middleware: ['auth', 'role-guard'], roles: ['admin'] })
7. Server-seitige Middleware und API-Schutz
Nuxt 3 hat neben den Client-seitigen Route-Middlewares auch eine Server-Middleware-Schicht unter server/middleware/. Diese Middleware läuft auf dem Nitro-Server für alle Requests – sowohl für /api/-Endpunkte als auch für SSR-Seitenaufrufe. Für den API-Schutz ist das der richtige Ort: Hier wird der Authorization-Header gelesen, das JWT verifiziert und der dekodierte Payload an den Event-Kontext angehängt, damit alle nachfolgenden API-Handler ihn konsumieren können.
Der entscheidende Unterschied zur Client-Middleware: Server-Middleware kann nicht manipuliert werden. Ein Angreifer, der die Client-Middleware umgeht (z.B. durch direkte API-Aufrufe mit curl), trifft auf die Server-Middleware als zweite Verteidigungslinie. Die Nuxt Middleware auf Serverseite verwendet getRequestHeader(event, 'authorization') um das Bearer-Token zu lesen, verifiziert es mit einem JWT-Secret aus der Nuxt-Runtimeconfig und gibt bei Fehlern einen 401-Fehler zurück. So ist jeder API-Endpunkt geschützt, ohne dass jeder Handler die Verifikation selbst implementieren muss.
8. Typische Fehler und wie man sie vermeidet
Der häufigste Fehler bei Nuxt Middleware für Auth: Das Token wird im localStorage gespeichert statt in einem HTTP-only Cookie. localStorage ist über JavaScript zugänglich und damit anfällig für XSS. Ein kompromittiertes Script in der App kann das Token auslesen und an einen Angreifer senden. HTTP-only Cookies sind vom JavaScript-Kontext nicht lesbar und werden automatisch bei Requests mitgeschickt. Der Refresh-Token sollte immer als HTTP-only Cookie übermittelt werden, das Access-Token kann im Arbeitsspeicher (Pinia-Store) gehalten werden und wird bei einem Seiten-Reload via Refresh erneuert.
Ein zweiter verbreiteter Fehler: Middleware wird nicht auf allen Seiten einer geschützten Sektion angewendet. Eine Layout-Komponente schützt nur die visuelle Darstellung, nicht die Route selbst. Wenn ein Nutzer direkt eine URL aufruft, läuft kein Layout-Code – nur die Middleware wird ausgeführt. Deshalb darf Zugriffsschutz ausschließlich in der Nuxt Middleware liegen, niemals allein in Layout-Komponenten oder onMounted-Hooks. Ein dritter Fehler: navigateTo wird ohne return aufgerufen, sodass die Middleware weiterläuft und der geschützte Inhalt doch geladen wird.
9. Middleware-Strategien im Vergleich
Für die Wahl der richtigen Nuxt Middleware-Strategie gibt es mehrere Faktoren: Granularität der Zugriffssteuerung, Performance-Overhead und Wartbarkeit. Die folgende Tabelle vergleicht die wichtigsten Ansätze.
| Strategie | Reichweite | Empfehlung | Einschränkung |
|---|---|---|---|
| Named Middleware | Explizit pro Seite | Auth-Guards, RBAC | Muss auf jeder Seite eingetragen sein |
| Global Middleware | Alle Routen automatisch | Session-Checks, Analytics | Läuft auch auf öffentlichen Seiten |
| Inline Middleware | Genau eine Seite | Einmaliger Sonderfall | Nicht wiederverwendbar |
| Server Middleware | Alle Server-Requests | API-Schutz, JWT-Verifikation | Kein Zugriff auf Pinia/Client-State |
| onMounted-Guard | Einzelne Komponente | Nicht empfohlen | Zu spät – Inhalt sichtbar vor Prüfung |
In der Praxis empfiehlt sich eine Kombination: Named Middleware auth für die Authentifizierungsprüfung, Named Middleware role-guard für RBAC und Server-Middleware für API-Schutz. Globale Middleware nur für wirklich universelle Aufgaben wie Session-Heartbeat oder Logging. Inline-Middleware vermeiden, da sie die Logik auf Seitenebene verstreut und die Wartbarkeit reduziert.
Mironsoft
Vue.js · Nuxt 3 · Auth-Architektur · API-Schutz
Sichere Auth-Architektur für eure Nuxt-Applikation?
Wir konzipieren und implementieren robuste Authentifizierungslösungen mit Nuxt Middleware – von JWT-Token-Management über RBAC bis zur serverseitigen API-Absicherung.
Auth-Architektur
JWT, Refresh-Token, HTTP-only Cookies und Pinia-Store-Integration für sichere Token-Verwaltung
RBAC-Implementierung
Rollenbasierte Middleware mit Route-Meta, serverseitige Verifikation und granulare Berechtigungen
Code-Review
Bestehende Auth-Implementierungen auf Sicherheitslücken, Race Conditions und XSS-Anfälligkeit prüfen
10. Zusammenfassung
Nuxt Middleware ist der richtige Ort für Authentifizierungslogik in Nuxt-3-Applikationen – nicht in Komponenten, nicht in onMounted-Hooks und nicht in Layout-Dateien. Die Named Middleware auth.ts schützt explizit markierte Routen, prüft Token-Gültigkeit und startet bei Bedarf einen stillen Refresh. Die RBAC-Middleware ergänzt die Authentifizierungsprüfung um rollenbasierte Zugriffskontrolle über Route-Meta-Daten. Server-Middleware unter server/middleware/ schützt die API-Ebene unabhängig vom Client.
Tokens gehören in HTTP-only Cookies oder den Arbeitsspeicher (Pinia), niemals in den localStorage. Refresh-Requests müssen durch ein Singleton-Promise serialisiert werden, um Race Conditions zu vermeiden. Und die entscheidende Grundregel: Client-seitige Nuxt Middleware ist kein Ersatz für serverseitige JWT-Verifikation – beide Schichten sind notwendig und ergänzen sich gegenseitig zu einem vollständigen Sicherheitskonzept.
Nuxt Middleware Auth — Das Wichtigste auf einen Blick
Route Guard
defineNuxtRouteMiddleware in middleware/auth.ts – prüft Token vor dem Rendern der Seite und leitet bei Bedarf zum Login weiter.
Token-Sicherheit
Refresh-Token als HTTP-only Cookie, Access-Token im Pinia-Store. Niemals localStorage für sensible Tokens verwenden.
RBAC
Rollen über definePageMeta({ roles: ['admin'] }) definieren, in der Middleware gegen to.meta.roles prüfen.
Server-Schutz
Server-Middleware unter server/middleware/ verifiziert JWT unabhängig vom Client – unverzichtbare zweite Sicherheitsebene.