Middleware

This document describes the authentication middleware system that protects routes and manages user access control throughout the application.

Overview

The authentication middleware is a critical security layer that automatically validates user authentication status for every route navigation. It implements a comprehensive protection system that handles token validation, automatic refresh, site-type specific access control, and intelligent redirection logic.

The middleware operates both on server-side and client-side, ensuring consistent authentication enforcement across the entire application lifecycle. It integrates seamlessly with the authentication composable and user store to provide a unified security experience.

Middleware Architecture

Global Authentication Middleware

graph TD
    A[Route Navigation] --> B[auth.global.ts]
    B --> C[Extract Cookies]
    C --> D[Decode JWT Token]
    D --> E{Token Valid?}
    E -->|Yes| F[Set Authenticated State]
    E -->|No| G[Check Refresh Token]
    G --> H{Refresh Valid?}
    H -->|Yes| I[Call Refresh API]
    H -->|No| J[Set Unauthenticated]
    I --> K{Refresh Success?}
    K -->|Yes| L[Update Tokens]
    K -->|No| J
    F --> M[Check Site Type]
    J --> M
    L --> M
    M --> N{Site Private?}
    N -->|Yes| O[Private Site Logic]
    N -->|No| P[Public Site Logic]
    O --> Q[Allow/Redirect]
    P --> Q
    Q --> R[Continue Navigation]

The global middleware (middleware/auth.global.ts) runs on every route change and provides comprehensive authentication checking with automatic token refresh and site-type aware access control.

Middleware Components

ComponentTypePurposeScope
auth.global.tsGlobalRoute protection and token validationAll routes
checkout.tsNamedCart status validation for checkoutCheckout flow
impersonate.tsNamedUser impersonation protectionImpersonation routes
server/middleware/auth.tsServerServer-side request processingAPI requests

Core Functionality

1. Token Validation and Management

// JWT Token Decoding and Validation
function decodeJwt(token: string): any | null {
  try {
    const parts = token.split(".");
    if (parts.length !== 3) return null;

    const payload = parts[1];
    let decodedPayload: string;

    // Universal decoding (server and client)
    if (process.server) {
      decodedPayload = Buffer.from(payload, "base64").toString("utf8");
    } else {
      decodedPayload = atob(payload);
    }

    return JSON.parse(decodedPayload);
  } catch (error) {
    console.error("JWT decoding error:", error);
    return null;
  }
}

// Token Validation Logic
const validateJwt = (token: string) => {
  if (!token) return false;
  const decoded = decodeJwt(token);
  return decoded?.exp && Date.now() / 1000 < decoded.exp;
};

2. Automatic Token Refresh

// Automatic Token Refresh System
async function checkAuthOnServer() {
  // Check if current access token is valid
  if (jwtCookie.value && validateJwt(jwtCookie.value)) {
    return true;
  }

  // Validate refresh token before attempting refresh
  if (refreshCookie.value && !validateJwt(refreshCookie.value)) {
    return false;
  }

  try {
    // Attempt token refresh
    const { data } = await useFetch("/api/auth/refresh", {
      method: "POST",
      body: {
        jwt: jwtCookie.value,
        refreshToken: refreshCookie.value,
      },
    });

    if (
      data.value?.isAuthenticated &&
      data.value.token &&
      data.value.refreshToken
    ) {
      // Update cookies with new tokens
      jwtCookie.value = data.value.token;
      refreshCookie.value = data.value.refreshToken;
      return true;
    }

    return false;
  } catch (error) {
    console.error("Token refresh failed:", error);
    return false;
  }
}

3. Site Type Management

The middleware supports two operational modes based on the frontMode configuration:

Private Site Mode

  • Default Behavior: All routes require authentication
  • Unauthenticated Access: Only authentication pages (/auth/*)
  • Redirect Logic: Unauthenticated users → Login page
  • Use Case: Internal business applications, admin panels

Public Site Mode

  • Default Behavior: Public access to most content
  • Protected Routes: Only routes with requiresAuth meta
  • Redirect Logic: Unauthenticated users → Home page
  • Use Case: E-commerce sites, marketing websites
// Site Type Configuration
const config = useRuntimeConfig();
const siteType = config.public.frontMode; // 'private' | 'public'

// Authentication-specific pages
const authPages = [
  "/auth/login",
  "/auth/register",
  "/auth/forgot-password",
  "/auth/reset-password",
];

// Redirection Logic
if (siteType === "private") {
  // Private site: redirect to login if not authenticated
  if (!isAuthenticated && !isAuthPage) {
    return navigateTo("/auth/login");
  }
} else if (siteType === "public") {
  // Public site: check route-specific authentication requirements
  if (!isAuthenticated && to.meta.requiresAuth) {
    return navigateTo("/");
  }
}

Route Protection Patterns

1. Global Protection (auth.global.ts)

export default defineNuxtRouteMiddleware(
  async (to: RouteLocationNormalized) => {
    // Extract authentication cookies
    const jwtCookie = useCookie("token");
    const refreshCookie = useCookie("refreshToken");
    const userStore = useUserStore();

    // Perform authentication check with automatic refresh
    const isAuthenticated = await checkAuthOnServer();

    // Update store and cookie state
    userStore.isAuthenticated = isAuthenticated;
    useCookie("isAuthenticated").value = isAuthenticated;

    // Apply site-specific protection logic
    if (isAuthenticated) {
      // Prevent access to auth pages when already authenticated
      if (isAuthPage) {
        return navigateTo("/");
      }
    } else {
      // Handle unauthenticated access based on site type
      if (siteType === "private" && !isAuthPage) {
        return navigateTo("/auth/login");
      }
      if (siteType === "public" && to.meta.requiresAuth) {
        return navigateTo("/");
      }
    }
  }
);

2. Specific Route Protection

Checkout Protection

// middleware/checkout.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { getCartStatus } = useCart();
  const cartStore = useCartStore();
  const cart = computed(() => cartStore.cart);

  if (cart.value) {
    const cartStatus = computed(() => getCartStatus(cart.value));

    // Only allow checkout for ongoing carts (not for quotes)
    if (cartStatus.value !== "ON_GOING" && !to.path.includes("/quotes")) {
      return navigateTo("/");
    }
  }
});

Impersonation Protection

// middleware/impersonate.ts
export default defineNuxtRouteMiddleware((to, from) => {
  // Prevent direct client-side access to impersonation routes
  if (process.client && to.path === "/impersonate") {
    window.location.replace("/");
  }
});

Server-Side Middleware

API Request Protection

// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
  // Currently minimal implementation
  // Future expansion for API-specific authentication
  return;
});

Future Enhancements:

  • API endpoint authentication validation
  • Request header verification
  • Rate limiting integration
  • API-specific error handling

Integration with Authentication System

Store Synchronization

// User Store Integration
const userStore = useUserStore();

// Update authentication state
userStore.isAuthenticated = isAuthenticated;

// Sync with cookie for client-side access
useCookie("isAuthenticated").value = isAuthenticated;

Composable Integration

// Integration with useDjustAuth composable
const { ensureAuthenticated } = useDjustAuth();

// Middleware uses same token validation logic
// Ensures consistency across authentication methods

Error Handling and Recovery

Token Refresh Failure

try {
  const refreshResult = await useFetch("/api/auth/refresh", {
    method: "POST",
    body: { jwt: jwtCookie.value, refreshToken: refreshCookie.value },
  });

  if (refreshResult.data.value?.isAuthenticated) {
    // Success: Update tokens and continue
    jwtCookie.value = refreshResult.data.value.token;
    refreshCookie.value = refreshResult.data.value.refreshToken;
  } else {
    // Failure: Mark as unauthenticated
    userStore.isAuthenticated = false;
  }
} catch (error) {
  console.error("Authentication check failed:", error);
  // Graceful degradation: assume unauthenticated
  userStore.isAuthenticated = false;
}

Network Failure Handling

// Graceful handling of network issues during authentication
catch (error) {
  console.error('Network error during auth check:', error);

  // Don't block navigation on network errors
  // Use existing token validity as fallback
  return jwtCookie.value && validateJwt(jwtCookie.value);
}

Performance Optimization

Efficient Token Validation

// Client-side validation first (faster)
if (jwtCookie.value && validateJwt(jwtCookie.value)) {
  return true; // Skip server round-trip
}

// Server validation only when necessary
// Reduces unnecessary API calls

Smart Refresh Logic

// Only attempt refresh if refresh token is valid
if (refreshCookie.value && !validateJwt(refreshCookie.value)) {
  return false; // Skip refresh attempt
}

// Prevents unnecessary API calls with expired refresh tokens

Security Considerations

XSS Protection

  • Tokens stored in HTTP-only cookies
  • No client-side token exposure
  • Secure cookie flags in production

CSRF Protection

  • SameSite cookie configuration
  • Request origin validation
  • Double-submit cookie patterns

Session Security

  • Automatic token rotation
  • Secure random token generation
  • Proper token expiration handling

Configuration Options

Runtime Configuration

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      frontMode: "private", // 'private' | 'public'
      // Other auth-related config
    },
  },
});

Route Meta Configuration

// pages/protected-page.vue
definePageMeta({
  requiresAuth: true, // For public sites
  middleware: "auth", // Explicit middleware application
});

Testing and Debugging

Development Debugging

// Enhanced logging for development
if (process.env.NODE_ENV === "development") {
  console.log("Auth middleware:", {
    route: to.path,
    authenticated: isAuthenticated,
    siteType,
    tokenValid: validateJwt(jwtCookie.value),
  });
}

Testing Scenarios

  1. Token Expiration: Verify automatic refresh works
  2. Invalid Tokens: Ensure proper cleanup and redirect
  3. Network Failures: Test graceful degradation
  4. Site Type Changes: Verify behavior in both modes
  5. Route Protection: Test protected/public route access

Best Practices

1. Middleware Ordering

// Ensure auth middleware runs before route-specific middleware
// Global middleware (auth.global.ts) runs automatically first

2. Error Handling

// Always provide fallback behavior
// Log errors for debugging
// Don't expose sensitive information

3. Performance

// Minimize API calls
// Use efficient token validation
// Implement proper caching strategies

4. Security

// Validate tokens thoroughly
// Handle edge cases securely
// Implement defense in depth

Troubleshooting

Common Issues

  1. Infinite Redirects: Check site type configuration and route meta
  2. Token Refresh Loops: Verify refresh token validation logic
  3. Store Desync: Ensure proper store updates in middleware
  4. Route Protection Gaps: Verify global middleware coverage

Debug Commands

// Check current authentication state
console.log("User Store:", useUserStore().isAuthenticated);
console.log("Cookie State:", useCookie("isAuthenticated").value);
console.log("Token:", useCookie("token").value);