Passport

Middleware & Route Protection

Protecting routes and managing authentication flows with middleware

Learn how to protect routes and manage authentication flows using Nuxt Umbu middleware with Laravel Passport.


Overview

Nuxt Umbu provides built-in middleware for protecting routes and managing authentication flows. These middleware automatically handle token validation, user authentication, and redirect logic.

Built-in Middleware

Auth Middleware

The auth middleware protects routes by requiring user authentication:

middleware/auth.ts
// This file is automatically provided by Nuxt Umbu
export default defineNuxtRouteMiddleware(async (to) => {
  const { loggedIn, fetchProfile } = useAuth()
  
  if (!loggedIn.value) {
    // Redirect to login page
    return navigateTo('/login')
  }
  
  try {
    // Verify token is still valid
    await fetchProfile('client')
  } catch (error) {
    // Token invalid, redirect to login
    return navigateTo('/login')
  }
})

Two-Factor Auth Middleware

The twoFactor middleware ensures 2FA verification when required:

middleware/twoFactor.ts
// This file is automatically provided by Nuxt Umbu
export default defineNuxtRouteMiddleware(async (to) => {
  const { loggedIn, user } = useAuth()
  
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
  
  // Check if user has 2FA enabled but not verified
  if (user.value?.two_factor_enabled && !user.value?.two_factor_verified) {
    return navigateTo('/2fa/verify')
  }
})

Using Middleware

Page-Level Protection

Protect individual pages using the definePageMeta macro:

pages/dashboard.vue
<template>
  <div>
    <h1>Dashboard</h1>
    <p>Welcome, {{ user?.name }}!</p>
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'umbu:auth' // Requires authentication
})

const { user } = useAuth()
</script>

Route-Level Protection

Configure middleware in your route definitions:

pages/admin/index.vue
<script setup>
definePageMeta({
  middleware: ['umbu:auth', 'umbu:two-factor'] // Multiple middleware
})
</script>

Layout-Level Protection

Apply middleware to entire layouts:

layouts/default.vue
<template>
  <div>
    <NuxtPage />
  </div>
</template>

<script setup>
definePageMeta({
  middleware: 'auth' // Apply to all pages using this layout
})
</script>

Custom Middleware

Role-Based Access Control

Create custom middleware for role-based protection:

middleware/admin.ts
export default defineNuxtRouteMiddleware(async (to) => {
  const { loggedIn, user } = useAuth()
  
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
  
  // Check if user has admin role
  if (!user.value?.roles?.includes('admin')) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Access denied'
    })
  }
})

Permission-Based Access

middleware/permission.ts
export default defineNuxtRouteMiddleware((to) => {
  const { loggedIn, user } = useAuth()
  const requiredPermission = to.meta.permission
  
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
  
  if (requiredPermission && !user.value?.permissions?.includes(requiredPermission)) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Insufficient permissions'
    })
  }
})

Subscription-Based Access

middleware/subscription.ts
export default defineNuxtRouteMiddleware(async (to) => {
  const { loggedIn, user } = useAuth()
  const requiredPlan = to.meta.plan
  
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
  
  if (requiredPlan) {
    const userPlan = user.value?.subscription?.plan
    
    if (!userPlan || userPlan !== requiredPlan) {
      return navigateTo('/billing/upgrade')
    }
  }
})

Advanced Middleware Patterns

Conditional Middleware

middleware/conditional.ts
export default defineNuxtRouteMiddleware(async (to) => {
  const { loggedIn, user } = useAuth()
  
  // Only apply to certain routes
  if (!to.path.startsWith('/admin')) {
    return
  }
  
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
  
  // Additional admin checks
  if (!user.value?.is_admin) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Admin access required'
    })
  }
})

Multi-Strategy Authentication

middleware/multi-strategy.ts
export default defineNuxtRouteMiddleware(async (to) => {
  const { loggedIn, strategy } = useAuth()
  const requiredStrategy = to.meta.strategy
  
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
  
  if (requiredStrategy && strategy !== requiredStrategy) {
    // Redirect to login with specific strategy
    return navigateTo(`/login?strategy=${requiredStrategy}`)
  }
})

API Route Protection

server/api/protected/[...].ts
import protectedMiddleware from '#auth-utils/server/middleware/protected'
import { getAuthSession } from '#auth-utils/server/utils'

export default defineEventHandler(async (event) => {
  // Use Nuxt Umbu's protected middleware for authentication
  await protectedMiddleware(event)
  
  // Get authentication session data
  const { token, strategyName } = getAuthSession(event)
  
  // User is authenticated, proceed with API logic
  return { data: 'Protected data', strategy: strategyName }
})

Route Configuration

Dynamic Route Protection

pages/[id]/edit.vue
<script setup>
definePageMeta({
  middleware: async (to) => {
    const { loggedIn, user } = useAuth()
    const resourceId = to.params.id
    
    if (!loggedIn.value) {
      return navigateTo('/login')
    }
    
    // Check if user owns this resource
    try {
      const resource = await $fetch(`/api/resources/${resourceId}`)
      if (resource.user_id !== user.value?.id) {
        throw createError({
          statusCode: 403,
          statusMessage: 'Access denied'
        })
      }
    } catch (error) {
      if (error.statusCode === 404) {
        throw createError({
          statusCode: 404,
          statusMessage: 'Resource not found'
        })
      }
      throw error
    }
  }
})
</script>

Public Route with Optional Auth

pages/profile/[username].vue
<script setup>
definePageMeta({
  middleware: async (to) => {
    const { loggedIn, fetchProfile } = useAuth()
    
    // Try to authenticate, but don't require it
    if (loggedIn.value) {
      try {
        await fetchProfile('client')
      } catch (error) {
        // Silently fail, user remains unauthenticated
        console.warn('Auth failed:', error)
      }
    }
  }
})
</script>

Middleware Configuration

Global Middleware Setup

nuxt.config.ts
export default defineNuxtConfig({
  modules: ['nuxt-umbu'],
  
  auth: {
    provider: 'passport',
    
    // Global middleware configuration
    globalMiddleware: {
      auth: ['auth'], // Apply to all routes
      guest: ['guest'] // Apply to guest routes
    }
  }
})

Custom Middleware Registration

plugins/middleware.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Register custom middleware
  addRouteMiddleware('custom-auth', async (to) => {
    const { loggedIn } = useAuth()
    
    if (!loggedIn.value && to.path !== '/login') {
      return navigateTo('/login')
    }
  }, { global: true })
})

Error Handling

Custom Error Pages

pages/error.vue
<template>
  <div class="error-page">
    <h1>{{ error?.statusCode }}</h1>
    <p>{{ error?.statusMessage }}</p>
    
    <div v-if="error?.statusCode === 401">
      <p>Please log in to access this page.</p>
      <NuxtLink to="/login">Go to Login</NuxtLink>
    </div>
    
    <div v-else-if="error?.statusCode === 403">
      <p>You don't have permission to access this page.</p>
      <NuxtLink to="/dashboard">Go to Dashboard</NuxtLink>
    </div>
    
    <div v-else>
      <p>Something went wrong.</p>
      <NuxtLink to="/">Go Home</NuxtLink>
    </div>
  </div>
</template>

<script setup>
const error = useError()
</script>

Middleware Error Handling

middleware/error-handling.ts
export default defineNuxtRouteMiddleware(async (to) => {
  try {
    const { loggedIn, fetchProfile } = useAuth()
    
    if (!loggedIn.value) {
      return navigateTo('/login')
    }
    
    await fetchProfile('client')
  } catch (error) {
    console.error('Middleware error:', error)
    
    // Clear invalid auth data
    const { logout } = useAuth()
    await logout('client')
    
    // Redirect with error message
    return navigateTo({
      path: '/login',
      query: { error: 'session_expired' }
    })
  }
})

Performance Optimization

Middleware Caching

middleware/cached-auth.ts
export default defineNuxtRouteMiddleware(async (to) => {
  const { loggedIn } = useAuth()
  return loggedIn.value ? undefined : navigateTo('/login')
})

Lazy Loading Middleware

middleware/lazy-auth.ts
export default defineNuxtRouteMiddleware(async (to) => {
  // Only run auth check for protected routes
  if (to.meta.public) {
    return
  }
  
  const { loggedIn } = useAuth()
  
  if (!loggedIn.value) {
    return navigateTo('/login')
  }
})

Testing Middleware

Unit Testing

tests/middleware/auth.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'

describe('Auth Middleware', () => {
  beforeEach(() => {
    // Mock Nuxt runtime and useAuth
    vi.mock('#imports', () => ({
      useAuth: () => ({
        loggedIn: { value: false },
        fetchProfile: vi.fn()
      })
    }))
  })

  it('redirects unauthenticated users', async () => {
    const { useAuth } = await import('#imports')
    const { loggedIn } = useAuth()
    
    const result = await global.$nuxt.$middleware.umbu.auth({
      path: '/protected',
      meta: {}
    })
    
    expect(result?.path).toBe('/login')
  })
  
  it('allows authenticated users', async () => {
    const { useAuth } = await import('#imports')
    const { loggedIn } = useAuth()
    loggedIn.value = true
    
    const result = await global.$nuxt.$middleware.umbu.auth({
      path: '/protected',
      meta: {}
    })
    
    expect(result).toBeUndefined()
  })
})

Next: Explore Examples for practical implementations and use cases.