port-nimara-client-portal/docs/404-and-session-fixes.md

7.1 KiB

404 and Session Expiration Fixes

Issues Addressed

  1. 404 Error on Expenses Page - The expenses page was returning a 404 error
  2. Session Expiration After 404 - Users were getting logged out after encountering the 404 error
  3. Immediate Session Expiration - Users were getting logged out immediately after logging in
  4. External Service 401 Errors - 401 errors from external services (CMS, database) were logging users out

Root Cause Analysis

404 Error Cause

  • The expenses page was missing the authorization middleware configuration
  • The dashboard layout referenced in the page metadata didn't exist
  • Nuxt wasn't properly configured to use layouts

Session Expiration Cause

  • The authentication middleware was incorrectly clearing the session cache on ALL errors (including 404s)
  • This caused a valid session to be invalidated when encountering any page error

Immediate Logout Cause

  • The authorization middleware was making its own API call, bypassing the session cache
  • The auth refresh plugin's 2-minute periodic validation was conflicting with the 3-minute session cache
  • Multiple concurrent session checks were causing race conditions

External Service 401 Cause

  • The auth error handler was treating ANY 401 error as a session expiration
  • When the CMS (cms.portnimara.dev) returned 401, it triggered a logout
  • The handler didn't distinguish between auth API errors and external service errors

Fixes Implemented

1. Fixed Expenses Page Metadata

File: pages/dashboard/expenses.vue

Added proper middleware configuration:

definePageMeta({
  middleware: ['authentication', 'authorization'],
  layout: 'dashboard',
  roles: ['sales', 'admin']
});

This ensures:

  • Authentication is checked first
  • Authorization checks for sales/admin roles
  • Proper layout is applied

2. Fixed Authentication Middleware

File: middleware/authentication.ts

Updated error handling to only clear cache on actual auth errors:

onResponseError({ response }) {
  // Clear cache only on actual auth errors, not 404s or other errors
  if (response.status === 401) {
    console.log('[MIDDLEWARE] Unauthorized error detected, clearing cache')
    sessionManager.clearCache();
    delete nuxtApp.payload.data?.authState;
  } else if (response.status === 403) {
    console.log('[MIDDLEWARE] Forbidden error detected, partial cache clear')
    // Don't clear cache on 403 as user is authenticated but lacks permissions
  }
  // Ignore 404s and other errors - they're not authentication issues
}

3. Enabled Layout Support

File: app.vue

Updated to support layouts:

<template>
  <NuxtPwaManifest />
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
  <GlobalToast />
</template>

4. Created Dashboard Layout

File: layouts/dashboard.vue

Created a full dashboard layout with:

  • Navigation drawer with role-based menu items
  • App bar showing user info and role badges
  • Proper logout functionality
  • Responsive design with rail mode
  • Safe auth state access to prevent initialization errors

5. Fixed Authorization Middleware

File: middleware/authorization.ts

Updated to use cached auth state instead of making API calls:

// Get auth state from authentication middleware (already cached)
const nuxtApp = useNuxtApp();
const authState = nuxtApp.payload?.data?.authState;

This prevents:

  • Duplicate API calls
  • Race conditions between middlewares
  • Session cache conflicts

6. Adjusted Auth Refresh Plugin

File: plugins/01.auth-refresh.client.ts

  • Changed periodic validation from 2 to 5 minutes to avoid conflicts with 3-minute cache
  • Added failure counting - only logs out after 3 consecutive failures
  • Increased random offset to prevent thundering herd

7. Fixed Auth Error Handler

File: plugins/02.auth-error-handler.client.ts

Updated to only handle 401/403 errors from application endpoints:

// Only handle authentication errors from our own API endpoints
const isAuthEndpoint = response.url && (
  response.url.includes('/api/auth/') ||
  response.url.includes('/api/') && !response.url.includes('cms.portnimara.dev') && !response.url.includes('database.portnimara.com')
)

// Handle authentication errors (401, 403) only from our API
if ((response.status === 401 || response.status === 403) && isAuthEndpoint) {
  // Clear auth and redirect
} else if (response.status === 401 && !isAuthEndpoint) {
  console.log('[AUTH_ERROR_HANDLER] Ignoring 401 from external service:', response.url)
  // Don't clear auth for external service 401s
}

This prevents external service authentication errors from logging users out.

Expected Results

  1. Expenses page should now load properly for users with sales or admin roles
  2. 404 errors won't cause session expiration - only actual authentication failures (401) will clear the session
  3. Better error handling - 403 errors (insufficient permissions) will redirect to dashboard with a message instead of logging out
  4. Consistent layout across all dashboard pages
  5. No immediate logout - Session checks are properly coordinated and cached
  6. Stable session management - No conflicts between different auth checking mechanisms
  7. External service errors ignored - 401 errors from CMS or database won't log users out

Testing Steps

  1. Log in with a user that has sales or admin role
  2. Navigate to /dashboard/expenses
  3. Verify the page loads without 404
  4. If you don't have the required role, you should be redirected to dashboard with an error message (not logged out)
  5. Try navigating to a non-existent page - you should get a 404 but remain logged in

Additional Improvements

  • The authorization middleware now stores error messages that are displayed via toast
  • The authorization middleware uses cached auth state instead of making API calls
  • The dashboard layout shows the current user and their role with safe access patterns
  • Navigation menu dynamically shows/hides items based on user roles
  • Session validation continues to work with the 3-minute cache + jitter to prevent race conditions
  • Auth refresh plugin runs validation every 5 minutes to avoid cache conflicts
  • Multiple failure tolerance prevents transient issues from logging users out
  • Auth error handler differentiates between app and external service errors

Timing Configuration Summary

  • Session Cache: 3 minutes (with 0-10 second jitter)
  • Auth Refresh Validation: Every 5 minutes (with 0-10 second offset)
  • Token Refresh: 5 minutes before token expiry
  • Failure Tolerance: 3 consecutive failures before logout

This configuration ensures no timing conflicts between different auth mechanisms.

External Service Integration

The auth error handler now properly handles errors from external services:

  • CMS errors (cms.portnimara.dev) - 401 errors are logged but don't trigger logout
  • Database errors (database.portnimara.com) - 401 errors are logged but don't trigger logout
  • App API errors (/api/*) - 401/403 errors still trigger logout as expected

This allows the app to gracefully handle authentication failures with integrated services without disrupting the user's main session.