Protecting Routes

Now that users can authenticate, let's protect certain pages so only signed-in users can access them.

Nuxt Middleware Overview

Nuxt provides a powerful middleware system that runs code before navigating to a route. We'll create an auth middleware that checks if the user is authenticated before allowing access to protected pages.

📁 Middleware Types

  • Named Middleware: Defined in middleware/ folder, applied per-page
  • Global Middleware: Runs on every route change (file name ends with .global.ts)
  • Inline Middleware: Defined directly in page's definePageMeta

Create Auth Middleware

Create middleware/auth.ts:

// middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to, from) => {
  const { isAuthenticated, checkAuth } = useAmplifyAuth();

  // Check auth status if not already loaded
  if (!isAuthenticated.value) {
    await checkAuth();
  }

  // If still not authenticated, redirect to sign in
  if (!isAuthenticated.value) {
    return navigateTo({
      path: '/auth/signin',
      query: {
        redirect: to.fullPath,
      },
    });
  }
});

Apply Middleware to Pages

Per-Page Protection

Add the middleware to any page that requires authentication using definePageMeta:

<script setup lang="ts">
definePageMeta({
  middleware: ['auth'],
});

// This page is now protected
</script>

<template>
  <div>
    <h1>Dashboard</h1>
    <p>This content is only visible to authenticated users.</p>
  </div>
</template>

Protecting Multiple Pages

You can use folder-based protection by creating a layout that applies the middleware to all pages using it:

// pages/dashboard/index.vue
definePageMeta({
  middleware: ['auth'],
});

// pages/dashboard/settings.vue
definePageMeta({
  middleware: ['auth'],
});

// pages/dashboard/profile.vue
definePageMeta({
  middleware: ['auth'],
});
💡
Pro Tip

You can apply middleware to all pages under a folder by creating a [...slug].vue catch-all route or using a shared layout with middleware.

Guest-Only Pages

Some pages should only be accessible to non-authenticated users (like sign-in and sign-up). Create a guest middleware:

// middleware/guest.ts
export default defineNuxtRouteMiddleware(async () => {
  const { isAuthenticated, checkAuth } = useAmplifyAuth();

  // Check auth status if not already loaded
  if (!isAuthenticated.value) {
    await checkAuth();
  }

  // If authenticated, redirect to home
  if (isAuthenticated.value) {
    return navigateTo('/');
  }
});

Apply to auth pages:

// pages/auth/signin.vue
definePageMeta({
  middleware: ['guest'],
  layout: 'auth',
});

// pages/auth/signup.vue
definePageMeta({
  middleware: ['guest'],
  layout: 'auth',
});

Role-Based Access Control

For more complex authorization, you might want to restrict access based on user roles or groups. Here's an example:

// middleware/admin.ts
export default defineNuxtRouteMiddleware(async () => {
  const { user, isAuthenticated, checkAuth } = useAmplifyAuth();

  if (!isAuthenticated.value) {
    await checkAuth();
  }

  if (!isAuthenticated.value) {
    return navigateTo('/auth/signin');
  }

  // Check if user belongs to admin group
  // Note: This requires fetching user groups from Cognito
  const userGroups = await fetchUserGroups();
  
  if (!userGroups.includes('admin')) {
    // User is not an admin, show unauthorized or redirect
    return navigateTo('/unauthorized');
  }
});
⚠️
Server-Side Authorization

Client-side route guards are for UX only! Always enforce authorization on the backend (in your API resolvers or Lambda functions) to prevent unauthorized access.

Complete Example: Protected Dashboard

Let's create a complete protected dashboard page:

// pages/dashboard.vue
<script setup lang="ts">
definePageMeta({
  middleware: ['auth'],
});

const { user, signOut } = useAmplifyAuth();

const handleSignOut = async () => {
  await signOut();
  navigateTo('/auth/signin');
};
</script>

<template>
  <div class="dashboard">
    <header class="dashboard-header">
      <h1>Dashboard</h1>
      <div class="user-info">
        <span>{{ user?.signInDetails?.loginId }}</span>
        <button @click="handleSignOut" class="btn-signout">
          Sign Out
        </button>
      </div>
    </header>

    <main class="dashboard-content">
      <div class="welcome-card">
        <h2>Welcome! 🎉</h2>
        <p>You're successfully authenticated.</p>
        <p class="user-id">
          User ID: <code>{{ user?.userId }}</code>
        </p>
      </div>

      <div class="next-steps">
        <h3>Next Steps</h3>
        <ul>
          <li>Set up your data models</li>
          <li>Create CRUD operations</li>
          <li>Build your app features</li>
        </ul>
      </div>
    </main>
  </div>
</template>

<style scoped>
.dashboard {
  min-height: 100vh;
  background: #f3f4f6;
}

.dashboard-header {
  background: white;
  padding: 1rem 2rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.dashboard-header h1 {
  margin: 0;
  font-size: 1.5rem;
}

.user-info {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.btn-signout {
  padding: 0.5rem 1rem;
  background: transparent;
  border: 1px solid #d1d5db;
  border-radius: 6px;
  cursor: pointer;
}

.btn-signout:hover {
  background: #f3f4f6;
}

.dashboard-content {
  padding: 2rem;
  max-width: 800px;
  margin: 0 auto;
}

.welcome-card {
  background: white;
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  margin-bottom: 1.5rem;
}

.welcome-card h2 {
  margin-top: 0;
}

.user-id {
  margin-bottom: 0;
  font-size: 0.875rem;
  color: #6b7280;
}

.user-id code {
  background: #f3f4f6;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-size: 0.75rem;
}

.next-steps {
  background: white;
  padding: 2rem;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.next-steps h3 {
  margin-top: 0;
}

.next-steps ul {
  margin-bottom: 0;
  padding-left: 1.5rem;
}

.next-steps li {
  margin-bottom: 0.5rem;
}
</style>

Testing Protected Routes

  1. Sign out if you're signed in
  2. Try to navigate directly to /dashboard
  3. You should be redirected to /auth/signin?redirect=%2Fdashboard
  4. Sign in with your credentials
  5. You should be redirected back to /dashboard

Summary

  • Nuxt middleware runs before route navigation
  • Create an auth middleware to protect pages
  • Create a guest middleware for auth pages
  • Apply middleware using definePageMeta
  • Always enforce authorization on the backend too
  • The redirect query parameter provides good UX after sign-in
🎉
Module Complete!

You now have a complete authentication system with sign up, sign in, email verification, and protected routes. In the next module, we'll add data storage using Amplify Data.