Skip to content

Nuxt Authentication and Authorization — Login, Sessions, and Protected Routes

DodaTech Updated 2026-06-28 7 min read

In this tutorial, you will learn about Nuxt Authentication and Authorization. We cover key concepts, practical examples, and best practices to help you master this topic.

Learn Nuxt authentication and authorization — implement user login with cookies, JWT tokens, session management, middleware-protected routes, and role-based access.

In this lesson, you'll understand how to add user authentication to your Nuxt application, protect routes, manage sessions, and control access based on user roles.

What You'll Learn

How to set up authentication with cookies and JWT, create login and registration pages, implement route middleware for protected pages, manage user sessions with server-side validation, and implement role-based authorization.

Why It Matters

Authentication is the foundation of user-specific features — personalized dashboards, admin panels, and member-only content. Without proper auth, you cannot protect sensitive data or provide personalized experiences.

Real-World Use

A SaaS analytics dashboard uses Nuxt auth with server-side token validation, role-based access (admin, viewer, editor), and automatic session refresh, processing 50,000+ authenticated requests daily.

flowchart TD
    A[User] --> B[Login Form]
    B --> C{Validate Credentials}
    C -->|Wrong| D[Error Message]
    C -->|Correct| E[Set Cookie/Token]
    E --> F[Redirect to Dashboard]
    F --> G[Protected Route]
    G --> H{Middleware Check}
    H -->|Valid Session| I[Render Page]
    H -->|Invalid| J[Redirect to Login]
    style A fill:#00dc82,color:#fff

Nuxt 3's useCookie composable makes cookie-based auth simple:

// composables/useAuth.ts
export const useAuth = () => {
  const token = useCookie('auth_token', {
    maxAge: 60 * 60 * 24 * 7, // 7 days
    secure: true,
    sameSite: 'lax',
    httpOnly: false // Accessible from client for API calls
  });

  const user = useState('user', () => null);

  async function login(email: string, password: string) {
    const { data, error } = await useFetch('/api/auth/login', {
      method: 'POST',
      body: { email, password }
    });

    if (error.value) {
      throw new Error(error.value.message);
    }

    token.value = data.value.token;
    user.value = data.value.user;
    return data.value;
  }

  async function logout() {
    token.value = null;
    user.value = null;
    await navigateTo('/login');
  }

  async function fetchUser() {
    if (!token.value) {
      user.value = null;
      return;
    }

    const { data } = await useFetch('/api/auth/me', {
      headers: {
        Authorization: `Bearer ${token.value}`
      }
    });

    user.value = data.value;
  }

  return { user, token, login, logout, fetchUser };
};

Expected output: A composable that manages authentication state with cookie-based tokens and automatic user session fetching.

Login Page

Create a login page with form validation:

<template>
  <form @submit.prevent="handleLogin" class="login-form">
    <h1>Sign In</h1>

    <div v-if="error" class="error-message">{{ error }}</div>

    <label>
      Email
      <input v-model="email" type="email" required autocomplete="email" />
    </label>

    <label>
      Password
      <input
        v-model="password"
        type="password"
        required
        autocomplete="current-password"
      />
    </label>

    <button type="submit" :disabled="loading">
      {{ loading ? 'Signing in...' : 'Sign In' }}
    </button>
  </form>
</template>

<script setup>
const { login } = useAuth();
const router = useRouter();

const email = ref('');
const password = ref('');
const error = ref('');
const loading = ref(false);

async function handleLogin() {
  error.value = '';
  loading.value = true;

  try {
    await login(email.value, password.value);
    await router.push('/dashboard');
  } catch (e) {
    error.value = e.message || 'Invalid credentials';
  } finally {
    loading.value = false;
  }
}
</script>

Expected output: A login form that authenticates the user, stores the token in a cookie, and redirects to the dashboard on success.

Server-Side Auth API

Create server-side auth endpoints:

// server/api/auth/login.post.ts
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';

export default defineEventHandler(async (event) => {
  const { email, password } = await readBody(event);

  // Find user (replace with database query)
  const user = await findUserByEmail(email);
  if (!user) {
    throw createError({
      statusCode: 401,
      message: 'Invalid credentials'
    });
  }

  // Verify password
  const valid = await bcrypt.compare(password, user.password);
  if (!valid) {
    throw createError({
      statusCode: 401,
      message: 'Invalid credentials'
    });
  }

  // Generate JWT
  const token = jwt.sign(
    { userId: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '7d' }
  );

  // Return user without password
  const { password: _, ...safeUser } = user;
  return { token, user: safeUser };
});
// server/api/auth/me.get.ts
export default defineEventHandler(async (event) => {
  const token = getHeader(event, 'authorization')?.replace('Bearer ', '');

  if (!token) {
    throw createError({ statusCode: 401, message: 'Not authenticated' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const user = await findUserById(decoded.userId);

    if (!user) {
      throw createError({ statusCode: 401, message: 'User not found' });
    }

    const { password: _, ...safeUser } = user;
    return safeUser;
  } catch (err) {
    throw createError({ statusCode: 401, message: 'Invalid token' });
  }
});

Expected output: Server endpoints that handle login with password hashing and return JWT tokens, plus a protected endpoint that validates tokens and returns user data.

Route Middleware for Protected Pages

Create auth middleware to protect routes:

// middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to, from) => {
  const { user, fetchUser } = useAuth();

  // Attempt to fetch user if not loaded
  if (!user.value) {
    await fetchUser();
  }

  // Redirect to login if not authenticated
  if (!user.value) {
    return navigateTo('/login', {
      redirectCode: 302,
      external: false
    });
  }
});

Apply middleware to protected pages:

<template>
  <!-- pages/dashboard.vue -->
  <div class="dashboard">
    <h1>Welcome, {{ user.name }}</h1>
    <p>Your role: {{ user.role }}</p>
  </div>
</template>

<script setup>
definePageMeta({
  middleware: ['auth']
});

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

Expected output: The dashboard page automatically checks authentication before rendering and redirects unauthenticated users to the login page.

Role-Based Authorization

Extend middleware for role-based access:

// middleware/role.ts
export default defineNuxtRouteMiddleware(async (to, from) => {
  const { user, fetchUser } = useAuth();

  if (!user.value) {
    await fetchUser();
  }

  if (!user.value) {
    return navigateTo('/login');
  }

  // Check required roles from route meta
  const requiredRoles = to.meta?.roles as string[];
  if (requiredRoles && requiredRoles.length > 0) {
    const hasRole = requiredRoles.includes(user.value.role);
    if (!hasRole) {
      return navigateTo('/unauthorized');
    }
  }
});
<template>
  <!-- pages/admin/index.vue -->
  <div class="admin-panel">
    <h1>Admin Panel</h1>
    <p>Only administrators can see this page.</p>
  </div>
</template>

<script setup>
definePageMeta({
  middleware: ['auth', 'role'],
  roles: ['admin']
});
</script>

Expected output: Admins can access the admin page while non-admin users are redirected to an unauthorized page.

Common Mistakes

  1. Storing tokens in localStorage instead of cookies: Tokens in localStorage are vulnerable to XSS Attacks. Cookies with httpOnly and secure flags are more secure, especially when validated server-side.

  2. Not validating tokens on the server: Client-side auth checks can be bypassed. Always validate tokens on the server using server routes or Nitro middleware before returning protected data.

  3. Exposing user passwords in API responses: Never return password hashes or sensitive fields from auth endpoints. Use object destructuring to exclude sensitive fields from responses.

  4. Missing CSRF protection for cookie-based auth: If using cookies without SameSite set properly, you're vulnerable to cross-site request forgery. Set sameSite: 'lax' or 'strict' on auth cookies.

  5. Not handling token expiration gracefully: When tokens expire mid-session, users see errors instead of being redirected to login. Implement automatic token refresh or clear the session and redirect on 401 responses.

Practice Questions

  1. What is the difference between authentication and authorization? Answer: Authentication verifies identity (who you are). Authorization determines access (what you can do). Auth confirms the user exists; authorization checks their permissions.

  2. Why should you validate tokens on the server? Answer: Client-side checks can be bypassed by malicious users. Server validation ensures the token is genuine, not expired, and corresponds to a valid user.

  3. How does httpOnly: true improve cookie security? Answer: It prevents JavaScript from reading the cookie, blocking XSS-based token theft. The cookie is only sent with HTTP requests, never exposed to client-side scripts.

  4. What does route middleware check before rendering a page? Answer: It checks the user's authentication state and role permissions. If the user is not authenticated or lacks the required role, it redirects to login or an unauthorized page.

Challenge

Build a complete auth system with: login and registration pages with input validation, JWT-based auth with 15-minute access tokens and 7-day refresh tokens, automatic token refresh on 401 responses, role-based access (admin, editor, viewer), protected API routes with server-side middleware, and a "remember me" option that persists sessions across browser restarts.

Mini Project

Create a user management dashboard with: login and registration forms with client and server validation, role-based access control (admin can manage users, editors can edit content, viewers can only read), protected API endpoints for user CRUD operations, session persistence across page reloads, logout that invalidates the token server-side, and an unauthorized page with a link back to the dashboard.

FAQ

Should I use JWT or session-based auth with Nuxt?

: Both work. JWT is stateless and scales horizontally without a session store. Session-based auth provides easier revocation. Choose JWT for APIs, sessions for server-rendered apps.

How do I handle password reset?

: Create a server route that sends a password reset email with a signed token. Validate the token on a reset page and update the password in the database. Token expiry adds security.

Can I use OAuth providers like Google or GitHub?

: Yes. The @sidebase/nuxt-auth module provides built-in OAuth support. Alternatively, use the OAuth flow manually with Nuxt server routes as the callback handler.

What is the best way to store user data client-side?

: Store only non-sensitive user data (name, email, role) in useState. Keep the token in an httpOnly cookie. Never store passwords or sensitive identifiers on the client.

How do I protect server-side API routes?

: Use Nitro middleware or check headers in each event handler. Call getHeader(event, 'authorization') to extract the token and validate it before processing the request.

What's Next

Learn about Nuxt Error Handling to create custom error pages, handle API errors gracefully, and implement error boundaries in your Nuxt application.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro