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'],
});
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');
}
});
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
- Sign out if you're signed in
- Try to navigate directly to
/dashboard - You should be redirected to
/auth/signin?redirect=%2Fdashboard - Sign in with your credentials
- You should be redirected back to
/dashboard
Summary
- Nuxt middleware runs before route navigation
- Create an
authmiddleware to protect pages - Create a
guestmiddleware for auth pages - Apply middleware using
definePageMeta - Always enforce authorization on the backend too
- The redirect query parameter provides good UX after sign-in
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.