<v/>
{ }
Vue.js · Nuxt 3 · Auth · Middleware · JWT
Nuxt Middleware, Auth und geschützte Bereiche
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.

15 Min. Lesezeit Nuxt 3 · defineNuxtRouteMiddleware · useAuth · JWT · RBAC Vue 3 · Pinia · Composables

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.

11. FAQ: Nuxt Middleware, Auth und geschützte Bereiche

1Was ist Nuxt Middleware bei Auth?
Funktionen, die vor dem Rendern einer Route laufen, das Token prüfen und unauthentifizierte Nutzer zum Login weiterleiten – bevor geschützte Inhalte sichtbar werden.
2Wo Access-Token speichern?
Im Pinia-Store (Arbeitsspeicher), nicht im localStorage. Refresh-Token in HTTP-only Cookie. localStorage ist XSS-anfällig.
3Race Conditions beim Refresh verhindern?
Singleton-Promise im Auth-Store: Alle parallelen Refresh-Anfragen warten auf dasselbe Promise statt neue Requests zu starten.
4RBAC mit Nuxt Middleware?
Rollen in definePageMeta({ roles: ['admin'] }) definieren. Middleware liest to.meta.roles und vergleicht mit Nutzerrolle aus dem Store.
5Reicht client-seitige Middleware als Schutz?
Nein. API-Endpunkte brauchen serverseitige JWT-Verifikation in server/middleware/ – unabhängig von der Client-Middleware.
6Named vs. global Middleware?
Named Middleware wird explizit aktiviert und schont öffentliche Seiten. Global läuft bei jedem Routenwechsel – nur für universelle Aufgaben sinnvoll.
7Nach Login zum Ursprungsziel weiterleiten?
Middleware übergibt to.fullPath als redirect-Query-Parameter. Login-Seite liest route.query.redirect und leitet nach Erfolg dorthin weiter.
8API-Routen mit Middleware schützen?
Server-Middleware unter server/middleware/ liest Authorization-Header, verifiziert JWT und hängt den Payload als event.context.user an.
9Middleware als Datei oder inline?
Immer als separate Datei unter middleware/. Testbar, wiederverwendbar, zentral wartbar. Inline nur für einmalige Sonderfälle.
10navigateTo ohne return – was passiert?
Die Middleware läuft nach navigateTo() weiter. Immer return navigateTo() schreiben, um den Ausführungsfluss zu stoppen.