# 🔐 Keycloak Custom Login Implementation Guide

## 📋 **Table of Contents**

1. [Overview & Architecture](#overview--architecture)
2. [Keycloak Configuration](#keycloak-configuration)
3. [Environment Setup](#environment-setup)
4. [Server-Side Implementation](#server-side-implementation)
5. [Client-Side Implementation](#client-side-implementation)
6. [Authentication Flow](#authentication-flow)
7. [Session Management](#session-management)
8. [Security Implementation](#security-implementation)
9. [Middleware System](#middleware-system)
10. [Error Handling](#error-handling)
11. [Testing & Debugging](#testing--debugging)
12. [Production Considerations](#production-considerations)
13. [Troubleshooting](#troubleshooting)

## 🏗️ **Overview & Architecture**

### **Why Custom Login Instead of Keycloak Hosted Pages**

**Benefits:**
- ✅ **Complete UI Control**: Custom branding, responsive design, mobile optimization
- ✅ **Better UX**: No redirects to external domains, seamless user experience  
- ✅ **Integration**: Easy integration with existing Nuxt/Vue components
- ✅ **Mobile Compatibility**: Full control over mobile browser behavior
- ✅ **Performance**: No external redirects, faster login experience

**Trade-offs:**
- ⚠️ **More Complex**: Must handle security, session management, token validation
- ⚠️ **Security Responsibility**: Must implement proper rate limiting, CSRF protection
- ⚠️ **Maintenance**: More code to maintain vs. Keycloak's hosted pages

### **Architecture Overview**

```mermaid
graph TD
    A[Client Browser] --> B[Nuxt App]
    B --> C[Custom Login Page]
    C --> D[Server API Route]
    D --> E[Keycloak Token Endpoint]
    E --> F[Return Access Token]
    F --> D
    D --> G[Create Encrypted Session]
    G --> H[Set HTTP-Only Cookie]
    H --> B
    B --> I[Authenticated App]
```

### **Tech Stack**

- **Frontend**: Nuxt 3 + Vue 3 + Vuetify 3
- **Backend**: Nuxt 3 Server Routes
- **Authentication**: Keycloak (Resource Owner Password Credentials flow)
- **Session**: Encrypted server-side session storage
- **Security**: Rate limiting, input validation, CSRF protection

## 🔧 **Keycloak Configuration**

### **1. Client Configuration**

In Keycloak Admin Console:

```javascript
// Client Settings
{
  clientId: "monacousa-portal",
  clientType: "confidential", // Important: Must be confidential
  standardFlowEnabled: false, // Disable standard flow
  directAccessGrantsEnabled: true, // Enable direct access grants
  serviceAccountsEnabled: false,
  publicClient: false,
  
  // Valid Redirect URIs (for OAuth fallback if needed)
  redirectUris: [
    "https://yourdomain.com/auth/callback",
    "http://localhost:3000/auth/callback" // Dev only
  ],
  
  // Root URL
  rootUrl: "https://yourdomain.com",
  adminUrl: "https://yourdomain.com",
  baseUrl: "https://yourdomain.com",
  
  // Advanced Settings
  accessTokenLifespan: "15 minutes",
  ssoSessionIdleTimeout: "30 minutes",
  ssoSessionMaxLifespan: "12 hours"
}
```

### **2. Required Scopes**

```javascript
// Default Client Scopes (include these)
[
  "openid",     // OpenID Connect
  "profile",    // User profile information  
  "email",      // Email address
  "roles",      // User roles/groups
  "groups"      // User groups (if using)
]
```

### **3. User Groups/Roles Setup**

```javascript
// Example Group Structure
{
  groups: [
    {
      name: "admin",
      description: "System administrators"
    },
    {
      name: "board", 
      description: "Board members"
    },
    {
      name: "user",
      description: "Regular users"
    }
  ]
}
```

### **4. Keycloak Client Secret**

```bash
# In Keycloak Admin Console:
# Clients → [Your Client] → Credentials → Client Secret
# Copy this secret for environment variables
```

## 🌐 **Environment Setup**

### **Environment Variables**

```bash
# .env
# Keycloak Configuration
NUXT_KEYCLOAK_ISSUER=https://auth.yourdomain.com/realms/your-realm
NUXT_KEYCLOAK_CLIENT_ID=your-client-id
NUXT_KEYCLOAK_CLIENT_SECRET=your-client-secret
NUXT_KEYCLOAK_CALLBACK_URL=https://yourdomain.com/auth/callback

# Session Security
NUXT_SESSION_SECRET=your-48-character-session-secret-key-here-123456
NUXT_ENCRYPTION_KEY=your-32-character-encryption-key-here12

# Public Configuration
NUXT_PUBLIC_DOMAIN=yourdomain.com
```

### **Nuxt Configuration**

```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // Private (server-side only)
    keycloak: {
      issuer: process.env.NUXT_KEYCLOAK_ISSUER,
      clientId: process.env.NUXT_KEYCLOAK_CLIENT_ID,
      clientSecret: process.env.NUXT_KEYCLOAK_CLIENT_SECRET,
      callbackUrl: process.env.NUXT_KEYCLOAK_CALLBACK_URL
    },
    sessionSecret: process.env.NUXT_SESSION_SECRET,
    encryptionKey: process.env.NUXT_ENCRYPTION_KEY,
    
    // Public (client-side accessible)
    public: {
      domain: process.env.NUXT_PUBLIC_DOMAIN
    }
  },
  
  // SSR Configuration for auth
  ssr: false, // or configure properly for SSR
  
  // Security headers
  nitro: {
    experimental: {
      wasm: true
    }
  }
})
```

## 🖥️ **Server-Side Implementation**

### **1. Direct Login API Route**

```typescript
// server/api/auth/direct-login.post.ts
export default defineEventHandler(async (event) => {
  try {
    const { username, password, rememberMe } = await readBody(event);
    const config = useRuntimeConfig();
    
    // Input validation
    if (!username || !password) {
      throw createError({
        statusCode: 400,
        statusMessage: 'Username and password are required'
      });
    }

    // Rate limiting
    const clientIP = getClientIP(event);
    if (!checkRateLimit(clientIP)) {
      throw createError({
        statusCode: 429,
        statusMessage: 'Too many login attempts'
      });
    }

    // Direct authentication with Keycloak
    const tokenResponse = await fetch(`${config.keycloak.issuer}/protocol/openid-connect/token`, {
      method: 'POST',
      headers: { 
        'Content-Type': 'application/x-www-form-urlencoded',
        'User-Agent': 'YourApp/1.0'
      },
      body: new URLSearchParams({
        grant_type: 'password',
        client_id: config.keycloak.clientId,
        client_secret: config.keycloak.clientSecret,
        username,
        password,
        scope: 'openid email profile roles'
      })
    });

    if (!tokenResponse.ok) {
      throw createError({
        statusCode: 401,
        statusMessage: 'Invalid credentials'
      });
    }

    const tokens = await tokenResponse.json();

    // Get user info from Keycloak
    const userResponse = await fetch(`${config.keycloak.issuer}/protocol/openid-connect/userinfo`, {
      headers: { 
        'Authorization': `Bearer ${tokens.access_token}`
      }
    });

    const userInfo = await userResponse.json();

    // Extract user groups and determine tier
    const groups = extractUserGroups(tokens, userInfo);
    const userTier = determineTier(groups);

    // Create session
    const sessionData = {
      user: {
        id: userInfo.sub,
        email: userInfo.email,
        name: userInfo.name,
        firstName: userInfo.given_name,
        lastName: userInfo.family_name,
        tier: userTier,
        groups: groups
      },
      tokens: {
        accessToken: tokens.access_token,
        refreshToken: tokens.refresh_token,
        expiresAt: Date.now() + (tokens.expires_in * 1000)
      },
      rememberMe: !!rememberMe,
      createdAt: Date.now(),
      lastActivity: Date.now()
    };

    // Create encrypted session cookie
    const sessionManager = createSessionManager();
    const cookieString = sessionManager.createSession(sessionData, !!rememberMe);
    
    // Set secure cookie
    const maxAge = !!rememberMe ? 60 * 60 * 24 * 30 : 60 * 60 * 24 * 7;
    const isProduction = process.env.NODE_ENV === 'production';
    
    setCookie(event, 'your-session-name', cookieString.split('=')[1], {
      httpOnly: true,
      secure: isProduction,
      sameSite: 'lax',
      maxAge,
      path: '/'
    });

    return {
      success: true,
      redirectTo: '/dashboard',
      user: {
        email: userInfo.email,
        name: userInfo.name,
        tier: userTier
      }
    };

  } catch (error) {
    // Error handling
    throw createError({
      statusCode: error.statusCode || 500,
      statusMessage: error.statusMessage || 'Authentication failed'
    });
  }
});
```

### **2. Session Validation API**

```typescript
// server/api/auth/session.get.ts
export default defineEventHandler(async (event) => {
  try {
    const sessionManager = createSessionManager();
    const sessionData = sessionManager.getSession(event);
    
    if (!sessionData || !sessionData.user) {
      return {
        authenticated: false,
        user: null
      };
    }
    
    // Check if token needs refresh
    if (sessionData.tokens.expiresAt < Date.now() + 60000) { // 1 minute buffer
      try {
        const refreshedTokens = await refreshKeycloakToken(sessionData.tokens.refreshToken);
        
        // Update session with new tokens
        sessionData.tokens = {
          accessToken: refreshedTokens.access_token,
          refreshToken: refreshedTokens.refresh_token,
          expiresAt: Date.now() + (refreshedTokens.expires_in * 1000)
        };
        
        sessionManager.updateSession(event, sessionData);
      } catch (refreshError) {
        console.warn('Token refresh failed:', refreshError);
        // Session is invalid, force logout
        return {
          authenticated: false,
          user: null
        };
      }
    }
    
    // Update last activity
    sessionData.lastActivity = Date.now();
    sessionManager.updateSession(event, sessionData);
    
    return {
      authenticated: true,
      user: sessionData.user
    };
    
  } catch (error) {
    console.error('Session validation error:', error);
    return {
      authenticated: false,
      user: null
    };
  }
});
```

### **3. Session Management Utilities**

```typescript
// server/utils/session.ts
import crypto from 'crypto';

interface SessionData {
  user: User;
  tokens: {
    accessToken: string;
    refreshToken: string;
    expiresAt: number;
  };
  rememberMe: boolean;
  createdAt: number;
  lastActivity: number;
}

export const createSessionManager = () => {
  const config = useRuntimeConfig();
  
  const encryptData = (data: any): string => {
    const cipher = crypto.createCipher('aes-256-cbc', config.encryptionKey);
    let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
    encrypted += cipher.final('hex');
    return encrypted;
  };

  const decryptData = (encryptedData: string): any => {
    try {
      const decipher = crypto.createDecipher('aes-256-cbc', config.encryptionKey);
      let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
      decrypted += decipher.final('utf8');
      return JSON.parse(decrypted);
    } catch (error) {
      return null;
    }
  };

  const createSession = (sessionData: SessionData, rememberMe: boolean): string => {
    const sessionId = crypto.randomUUID();
    const encryptedData = encryptData(sessionData);
    
    // Store in server-side storage (memory, database, Redis, etc.)
    serverSessionStorage.set(sessionId, encryptedData);
    
    return `session-id=${sessionId}`;
  };

  const getSession = (event: any): SessionData | null => {
    const sessionId = getCookie(event, 'your-session-name');
    if (!sessionId) return null;
    
    const encryptedData = serverSessionStorage.get(sessionId);
    if (!encryptedData) return null;
    
    return decryptData(encryptedData);
  };

  return {
    createSession,
    getSession,
    updateSession: (event: any, sessionData: SessionData) => {
      const sessionId = getCookie(event, 'your-session-name');
      if (sessionId) {
        const encryptedData = encryptData(sessionData);
        serverSessionStorage.set(sessionId, encryptedData);
      }
    },
    destroySession: (event: any) => {
      const sessionId = getCookie(event, 'your-session-name');
      if (sessionId) {
        serverSessionStorage.delete(sessionId);
        deleteCookie(event, 'your-session-name');
      }
    }
  };
};
```

## 🎨 **Client-Side Implementation**

### **1. Authentication Composable**

```typescript
// composables/useAuth.ts
export const useAuth = () => {
  // Use useState for SSR compatibility
  const user = useState<User | null>('auth.user', () => null);
  const isAuthenticated = computed(() => !!user.value);
  const loading = ref(false);
  const error = ref<string | null>(null);

  // Tier-based computed properties
  const userTier = computed(() => user.value?.tier || 'user');
  const isUser = computed(() => user.value?.tier === 'user');
  const isBoard = computed(() => user.value?.tier === 'board');
  const isAdmin = computed(() => user.value?.tier === 'admin');

  // Login method
  const login = async (credentials: LoginCredentials) => {
    loading.value = true;
    error.value = null;
    
    try {
      const response = await $fetch<LoginResponse>('/api/auth/direct-login', {
        method: 'POST',
        body: credentials,
        timeout: 30000
      });
      
      if (response.success) {
        // Wait for cookie to be set
        await new Promise(resolve => setTimeout(resolve, 200));
        
        // Verify session was created properly
        let sessionSuccess = false;
        let attempts = 0;
        const maxAttempts = 3;
        
        while (!sessionSuccess && attempts < maxAttempts) {
          attempts++;
          sessionSuccess = await checkAuth();
          
          if (!sessionSuccess && attempts < maxAttempts) {
            await new Promise(resolve => setTimeout(resolve, 500));
          }
        }
        
        if (sessionSuccess) {
          return { 
            success: true, 
            redirectTo: response.redirectTo || '/dashboard' 
          };
        }
      }
      
      return { success: false, error: 'Login failed' };
    } catch (err: any) {
      const errorMessage = getErrorMessage(err);
      error.value = errorMessage;
      return { success: false, error: errorMessage };
    } finally {
      loading.value = false;
    }
  };

  // Check authentication status
  const checkAuth = async (): Promise<boolean> => {
    try {
      const response = await $fetch<SessionResponse>('/api/auth/session');
      
      if (response.authenticated && response.user) {
        user.value = response.user;
        return true;
      } else {
        user.value = null;
        return false;
      }
    } catch (err) {
      user.value = null;
      return false;
    }
  };

  // Logout method
  const logout = async () => {
    try {
      await $fetch('/api/auth/logout', { method: 'POST' });
      user.value = null;
      await navigateTo('/login');
    } catch (err) {
      user.value = null;
      await navigateTo('/login');
    }
  };

  return {
    user: readonly(user),
    isAuthenticated,
    loading: readonly(loading),
    error: readonly(error),
    userTier,
    isUser,
    isBoard,
    isAdmin,
    login,
    logout,
    checkAuth
  };
};
```

### **2. Custom Login Page**

```vue
<!-- pages/login.vue -->
<template>
  <div class="login-container">
    <v-container fluid class="fill-height">
      <v-row class="fill-height" justify="center" align="center">
        <v-col cols="12" sm="8" md="6" lg="4" xl="3">
          <v-card class="login-card" elevation="24">
            <v-card-text class="pa-8">
              <!-- Logo and Branding -->
              <div class="text-center mb-8">
                <v-img 
                  src="/logo.png" 
                  width="120" 
                  height="120"
                  class="mx-auto mb-4" 
                  alt="Company Logo"
                />
                <h1 class="text-h4 font-weight-bold mb-2">
                  Welcome Back
                </h1>
                <p class="text-body-1 text-medium-emphasis">
                  Sign in to your portal
                </p>
              </div>

              <!-- Login Form -->
              <v-form @submit.prevent="handleLogin" ref="loginForm">
                <v-text-field
                  v-model="credentials.username"
                  label="Username or Email"
                  prepend-inner-icon="mdi-account"
                  variant="outlined"
                  :error-messages="errors.username"
                  :disabled="loading"
                  class="mb-3"
                  required
                />
                
                <v-text-field
                  v-model="credentials.password"
                  :type="showPassword ? 'text' : 'password'"
                  label="Password"
                  prepend-inner-icon="mdi-lock"
                  :append-inner-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
                  @click:append-inner="showPassword = !showPassword"
                  variant="outlined"
                  :error-messages="errors.password"
                  :disabled="loading"
                  class="mb-3"
                  required
                />

                <div class="d-flex justify-space-between align-center mb-6">
                  <v-checkbox 
                    v-model="credentials.rememberMe" 
                    label="Remember me"
                    density="compact"
                    :disabled="loading"
                  />
                </div>

                <!-- Error Alert -->
                <v-alert
                  v-if="loginError"
                  type="error"
                  variant="tonal"
                  class="mb-4"
                  closable
                  @click:close="loginError = ''"
                >
                  {{ loginError }}
                </v-alert>

                <!-- Login Button -->
                <v-btn
                  type="submit"
                  color="primary"
                  size="large"
                  block
                  :loading="loading"
                  :disabled="!isFormValid"
                  class="mb-4"
                >
                  <v-icon left>mdi-login</v-icon>
                  Sign In
                </v-btn>
              </v-form>
            </v-card-text>
          </v-card>
        </v-col>
      </v-row>
    </v-container>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  layout: false,
  middleware: 'guest'  // Prevents authenticated users from accessing login
});

const { login, loading: authLoading, error: authError } = useAuth();

// Form state
const credentials = ref({
  username: '',
  password: '',
  rememberMe: false
});

const showPassword = ref(false);
const loginError = ref('');
const errors = ref({
  username: '',
  password: ''
});

const loading = computed(() => authLoading.value);
const isFormValid = computed(() => {
  return credentials.value.username.length > 0 && 
         credentials.value.password.length > 0 && 
         !loading.value;
});

// Form validation
const validateForm = () => {
  errors.value = { username: '', password: '' };
  let isValid = true;

  if (!credentials.value.username || credentials.value.username.length < 2) {
    errors.value.username = 'Username must be at least 2 characters';
    isValid = false;
  }

  if (!credentials.value.password || credentials.value.password.length < 6) {
    errors.value.password = 'Password must be at least 6 characters';
    isValid = false;
  }

  return isValid;
};

// Handle login submission
const handleLogin = async () => {
  if (!validateForm()) return;

  loginError.value = '';

  try {
    const result = await login({
      username: credentials.value.username,
      password: credentials.value.password,
      rememberMe: credentials.value.rememberMe
    });

    if (result.success) {
      await navigateTo(result.redirectTo || '/dashboard');
    } else {
      loginError.value = result.error || 'Login failed. Please check your credentials.';
    }
  } catch (error: any) {
    loginError.value = authError.value || 'Login failed. Please try again.';
  }
};

// Auto-focus username field
onMounted(() => {
  nextTick(() => {
    const usernameField = document.querySelector('input[type="text"]') as HTMLInputElement;
    if (usernameField) {
      usernameField.focus();
    }
  });
});
</script>
```

### **3. Auth Plugin (Initialize Auth State)**

```typescript
// plugins/01.auth-check.client.ts
export default defineNuxtPlugin(async () => {
  const { checkAuth } = useAuth();
  
  // Check authentication status on app startup
  await checkAuth();
});
```

## 🛡️ **Security Implementation**

### **1. Rate Limiting**

```typescript
// server/utils/security.ts
const loginAttempts = new Map<string, { count: number; lastAttempt: number }>();
const blockedIPs = new Map<string, number>();

export const checkRateLimit = (ip: string): { allowed: boolean; attemptsLeft: number } => {
  const now = Date.now();
  const maxAttempts = 5;
  const windowMs = 15 * 60 * 1000; // 15 minutes
  const blockDurationMs = 60 * 60 * 1000; // 1 hour

  // Check if IP is blocked
  const blockedUntil = blockedIPs.get(ip);
  if (blockedUntil && now < blockedUntil) {
    return { allowed: false, attemptsLeft: 0 };
  }

  // Clear expired blocks
  if (blockedUntil && now >= blockedUntil) {
    blockedIPs.delete(ip);
  }

  // Check current attempts
  const attempts = loginAttempts.get(ip);
  if (attempts && (now - attempts.lastAttempt) > windowMs) {
    loginAttempts.delete(ip);
    return { allowed: true, attemptsLeft: maxAttempts };
  }

  const currentCount = attempts?.count || 0;
  if (currentCount >= maxAttempts) {
    blockedIPs.set(ip, now + blockDurationMs);
    return { allowed: false, attemptsLeft: 0 };
  }

  return { allowed: true, attemptsLeft: maxAttempts - currentCount };
};

export const recordFailedAttempt = (ip: string): void => {
  const now = Date.now();
  const attempts = loginAttempts.get(ip);
  
  if (attempts) {
    attempts.count += 1;
    attempts.lastAttempt = now;
  } else {
    loginAttempts.set(ip, { count: 1, lastAttempt: now });
  }
};
```

### **2. Input Validation**

```typescript
// server/utils/validation.ts
export const validateLoginInput = (username: string, password: string): string[] => {
  const errors: string[] = [];
  
  // Username validation
  if (!username || typeof username !== 'string') {
    errors.push('Username is required');
  } else if (username.length < 2) {
    errors.push('Username must be at least 2 characters');
  } else if (username.length > 100) {
    errors.push('Username is too long');
  } else if (!/^[a-zA-Z0-9@._-]+$/.test(username)) {
    errors.push('Username contains invalid characters');
  }
  
  // Password validation
  if (!password || typeof password !== 'string') {
    errors.push('Password is required');
  } else if (password.length < 6) {
    errors.push('Password must be at least 6 characters');
  } else if (password.length > 200) {
    errors.push('Password is too long');
  }
  
  return errors;
};

export const getClientIP = (event: any): string => {
  const headers = getHeaders(event);
  return (
    headers['x-forwarded-for']?.split(',')[0]?.trim() ||
    headers['x-real-ip'] ||
    headers['x-client-ip'] ||
    headers['cf-connecting-ip'] ||
    'unknown'
  );
};
```

### **3. Session Security**

```typescript
// Secure cookie configuration
setCookie(event, 'session-name', sessionId, {
  httpOnly: true,           // Prevent XSS attacks
  secure: isProduction,     // HTTPS only in production
  sameSite: 'lax',         // CSRF protection
  maxAge: rememberMe ? 2592000 : 604800, // 30 days vs 7 days
  path: '/',               // Available site-wide
  domain: process.env.NUXT_PUBLIC_DOMAIN ? `.${process.env.NUXT_PUBLIC_DOMAIN}` : undefined
});
```

## 🔄 **Authentication Flow**

### **Complete Authentication Flow Diagram**

```mermaid
sequenceDiagram
    participant U as User
    participant C as Client App
    participant S as Server API
    participant K as Keycloak
    participant D as Database

    U->>C: Navigate to /login
    C->>C: Guest middleware (allow if not authenticated)
    C->>U: Show login form

    U->>C: Submit credentials
    C->>S: POST /api/auth/direct-login
    S->>S: Validate input & rate limiting
    S->>K: POST /token (ROPC flow)
    K->>S: Return access token + refresh token
    S->>K: GET /userinfo (with access token)
    K->>S: Return user profile
    S->>S: Extract groups/roles, determine tier
    S->>D: Create encrypted session
    S->>C: Set secure cookie + return success
    C->>C: Navigate to /dashboard
    C->>S: GET /api/auth/session (via cookie)
    S->>S: Decrypt session, validate tokens
    S->>C: Return user data
    C->>C: Auth middleware passes
    C->>U: Show authenticated dashboard
```

### **Session Lifecycle**

1. **Login**: User provides credentials → Server validates with Keycloak → Creates encrypted session
2. **Session Check**: Client calls `/api/auth/session` → Server validates session → Returns user data
3. **Token Refresh**: Server automatically refreshes tokens before expiry
4. **Logout**: Client calls `/api/auth/logout` → Server destroys session → Redirects to login

## 🛠️ **Middleware System**

### **1. Auth Middleware (Protect Routes)**

```typescript
// middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to) => {
  if (to.meta.auth === false) {
    return; // Skip auth for public pages
  }

  const { isAuthenticated, checkAuth, user } = useAuth();
  
  // Ensure auth is checked if user isn't loaded
  if (!user.value) {
    await checkAuth();
  }

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

### **2. Guest Middleware (Redirect Authenticated Users)**

```typescript
// middleware/guest.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { user } = useAuth();
  
  // If user is already authenticated, redirect to dashboard
  if (user.value) {
    return navigateTo('/dashboard');
  }
});
```

### **3. Role-Based Middleware**

```typescript
// middleware/auth-admin.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { isAuthenticated, isAdmin } = useAuth();
  
  if (!isAuthenticated.value) {
    return navigateTo('/login');
  }
  
  if (!isAdmin.value) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Access denied. Administrator privileges required.'
    });
  }
});
```

## 🧪 **Testing & Debugging**

### **1. Testing Checklist**

**Backend Testing:**
```bash
# Test direct login endpoint
curl -X POST http://localhost:3000/api/auth/direct-login \
  -H "Content-Type: application/json" \
  -d '{"username":"test@example.com","password":"password123"}'

# Test session endpoint with cookie
curl -X GET http://localhost:3000/api/auth/session \
  -H "Cookie: your-session-name=session-id-here"

# Test logout endpoint
curl -X POST http://localhost:3000/api/auth/logout \
  -H "Cookie: your-session-name=session-id-here"
```

**Frontend Testing:**
- [ ] **Login Form Validation**: Test empty fields, short passwords, invalid characters
- [ ] **Login Flow**: Valid credentials → successful login → redirect to dashboard
- [ ] **Error Handling**: Invalid credentials → proper error messages
- [ ] **Session Management**: Page refresh → user stays logged in
- [ ] **Mobile Compatibility**: Test on iOS Safari, Android Chrome
- [ ] **Remember Me**: Long-term sessions work correctly

**Security Testing:**
- [ ] **Rate Limiting**: 5+ failed attempts → IP blocked for 1 hour
- [ ] **CSRF Protection**: Direct API calls without cookies → rejected
- [ ] **XSS Protection**: Malicious input → properly sanitized
- [ ] **Session Security**: HttpOnly cookies → not accessible via JavaScript

### **2. Debug Tools**

**Server-Side Debugging:**
```typescript
// Add debug logging in server routes
console.log('🔄 Login attempt:', {
  username: credentials.username,
  ip: getClientIP(event),
  timestamp: new Date().toISOString()
});

console.log('✅ Keycloak response:', {
  status: tokenResponse.status,
  hasTokens: !!tokens.access_token,
  userEmail: userInfo.email,
  groups: extractedGroups
});
```

**Client-Side Debugging:**
```typescript
// Add debug logging in composables
const checkAuth = async (): Promise<boolean> => {
  console.log('🔄 Checking authentication...');
  
  try {
    const response = await $fetch<SessionResponse>('/api/auth/session');
    
    console.log('🔍 Session response:', {
      authenticated: response.authenticated,
      user: response.user?.email,
      tier: response.user?.tier
    });
    
    // ... rest of method
  } catch (err) {
    console.error('❌ Auth check failed:', err);
    // ... error handling
  }
};
```

**Browser DevTools Inspection:**
- **Network Tab**: Monitor API calls to `/api/auth/*`
- **Application Tab**: Check cookies are set correctly
- **Console Tab**: Review authentication logs
- **Sources Tab**: Set breakpoints in auth flow

## 🚨 **Error Handling**

### **1. Server-Side Error Handling**

```typescript
// server/api/auth/direct-login.post.ts
export default defineEventHandler(async (event) => {
  try {
    // ... authentication logic
    
  } catch (error: any) {
    console.error('❌ Login error:', {
      message: error.message,
      status: error.status,
      ip: getClientIP(event),
      timestamp: new Date().toISOString()
    });

    // Map specific errors to user-friendly messages
    if (error.status === 401) {
      recordFailedAttempt(getClientIP(event));
      
      throw createError({
        statusCode: 401,
        statusMessage: 'Invalid username or password'
      });
    }
    
    if (error.status === 429) {
      throw createError({
        statusCode: 429,
        statusMessage: 'Too many login attempts. Please try again later.'
      });
    }
    
    if (error.code === 'ECONNREFUSED') {
      throw createError({
        statusCode: 503,
        statusMessage: 'Authentication service temporarily unavailable'
      });
    }
    
    // Generic error for unexpected issues
    throw createError({
      statusCode: 500,
      statusMessage: 'Login failed. Please try again.'
    });
  }
});
```

### **2. Client-Side Error Handling**

```typescript
// composables/useAuth.ts
const getErrorMessage = (err: any): string => {
  // Handle network errors
  if (!err.response) {
    return 'Network error. Please check your connection.';
  }
  
  // Handle HTTP errors
  switch (err.status) {
    case 400:
      return err.data?.message || 'Invalid request. Please check your input.';
    case 401:
      return 'Invalid username or password.';
    case 403:
      return 'Access denied. Please contact your administrator.';
    case 429:
      return 'Too many login attempts. Please try again later.';
    case 502:
    case 503:
      return 'Service temporarily unavailable. Please try again.';
    case 504:
      return 'Request timeout. Please try again.';
    default:
      return err.data?.message || 'Login failed. Please try again.';
  }
};
```

### **3. User-Friendly Error Display**

```vue
<!-- Login page error handling -->
<v-alert
  v-if="loginError"
  type="error"
  variant="tonal"
  class="mb-4"
  closable
  @click:close="loginError = ''"
>
  <template v-slot:prepend>
    <v-icon>mdi-alert-circle</v-icon>
  </template>
  <div>
    <strong>Login Failed</strong>
    <br>
    {{ loginError }}
  </div>
  <template v-slot:append v-if="showRetryButton">
    <v-btn
      variant="text"
      size="small"
      @click="retryLogin"
    >
      Retry
    </v-btn>
  </template>
</v-alert>
```

## 🚀 **Production Considerations**

### **1. Environment Configuration**

```bash
# Production .env
NODE_ENV=production

# Use strong secrets in production
NUXT_SESSION_SECRET=generate-a-very-strong-48-character-secret-key-here
NUXT_ENCRYPTION_KEY=generate-strong-32-char-key-here

# Keycloak production URLs
NUXT_KEYCLOAK_ISSUER=https://auth.yourdomain.com/realms/your-realm
NUXT_KEYCLOAK_CLIENT_ID=your-production-client
NUXT_KEYCLOAK_CLIENT_SECRET=your-production-secret

# Security settings
NUXT_PUBLIC_DOMAIN=yourdomain.com
```

### **2. Security Headers**

```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    routeRules: {
      '/**': {
        headers: {
          // Security headers
          'X-Frame-Options': 'DENY',
          'X-Content-Type-Options': 'nosniff',
          'X-XSS-Protection': '1; mode=block',
          'Referrer-Policy': 'strict-origin-when-cross-origin',
          'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
          
          // HTTPS enforcement
          'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
          
          // Content Security Policy
          'Content-Security-Policy': [
            "default-src 'self'",
            "script-src 'self' 'unsafe-inline'",
            "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
            "font-src 'self' https://fonts.gstatic.com",
            "img-src 'self' data: https:",
            "connect-src 'self'"
          ].join('; ')
        }
      }
    }
  }
});
```

### **3. Session Storage Options**

**In-Memory (Development Only):**
```typescript
// Simple Map - loses data on restart
const sessions = new Map<string, string>();
```

**Redis (Recommended for Production):**
```typescript
// server/utils/session-storage.ts
import Redis from 'ioredis';

const redis = new Redis({
  host: process.env.REDIS_HOST || 'localhost',
  port: parseInt(process.env.REDIS_PORT || '6379'),
  password: process.env.REDIS_PASSWORD,
  db: 0,
  retryDelayOnFailover: 100,
  maxRetriesPerRequest: 3
});

export const sessionStorage = {
  async set(key: string, value: string, ttl: number = 3600): Promise<void> {
    await redis.setex(`session:${key}`, ttl, value);
  },
  
  async get(key: string): Promise<string | null> {
    return await redis.get(`session:${key}`);
  },
  
  async delete(key: string): Promise<void> {
    await redis.del(`session:${key}`);
  }
};
```

**Database (Alternative):**
```sql
-- Session table schema
CREATE TABLE user_sessions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  session_id VARCHAR(255) UNIQUE NOT NULL,
  user_id VARCHAR(255) NOT NULL,
  session_data TEXT NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX idx_sessions_session_id ON user_sessions(session_id);
CREATE INDEX idx_sessions_expires_at ON user_sessions(expires_at);
```

### **4. Monitoring & Logging**

```typescript
// server/utils/monitoring.ts
export const logAuthEvent = (event: string, data: any) => {
  const logData = {
    timestamp: new Date().toISOString(),
    event,
    ip: data.ip || 'unknown',
    userAgent: data.userAgent || 'unknown',
    user: data.user || 'anonymous',
    success: data.success || false,
    error: data.error || null
  };
  
  // Send to monitoring service (e.g., DataDog, New Relic, etc.)
  console.log(JSON.stringify(logData));
  
  // In production, send to your logging service:
  // await sendToLoggingService(logData);
};

// Usage in auth routes
logAuthEvent('login_attempt', {
  ip: getClientIP(event),
  user: username,
  success: true
});
```

### **5. Performance Optimization**

```typescript
// Cache Keycloak public key for token validation
let keycloakPublicKey: string | null = null;
let keyLastFetched = 0;
const KEY_CACHE_DURATION = 3600000; // 1 hour

const getKeycloakPublicKey = async (): Promise<string> => {
  const now = Date.now();
  
  if (!keycloakPublicKey || (now - keyLastFetched) > KEY_CACHE_DURATION) {
    const config = useRuntimeConfig();
    const response = await fetch(`${config.keycloak.issuer}/.well-known/openid_configuration`);
    const oidcConfig = await response.json();
    
    const keysResponse = await fetch(oidcConfig.jwks_uri);
    const keys = await keysResponse.json();
    
    // Extract public key from JWKS
    keycloakPublicKey = keys.keys[0].x5c[0];
    keyLastFetched = now;
  }
  
  return keycloakPublicKey!;
};
```

## 🔧 **Troubleshooting**

### **1. Common Issues**

**Issue: "Invalid client credentials"**
```bash
# Solution: Check Keycloak client configuration
- Verify client ID matches environment variable
- Ensure client secret is correct
- Confirm client type is "confidential"
- Check "Direct Access Grants" is enabled
```

**Issue: "CORS errors during login"**
```bash
# Solution: Configure Keycloak CORS settings
- In Keycloak Admin Console
- Go to Client → Settings → Advanced Settings
- Add your domain to "Web Origins"
- Set "Valid Redirect URIs" properly
```

**Issue: "Session not persisting after login"**
```typescript
// Solution: Check cookie configuration
setCookie(event, 'session-name', sessionId, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production', // Important!
  sameSite: 'lax',
  domain: process.env.NODE_ENV === 'production' ? '.yourdomain.com' : undefined
});
```

**Issue: "Redirect loop after login"**
```typescript
// Solution: Check middleware configuration
// Ensure pages have correct middleware:
- Login page: middleware: 'guest'
- Dashboard pages: middleware: 'auth'
- Index page: client-side navigation only
```

**Issue: "User groups/roles not being extracted"**
```typescript
// Solution: Configure group mappers in Keycloak
// 1. Go to Client Scopes → roles → Mappers
// 2. Create mapper: "groups"
// 3. Mapper Type: "Group Membership"
// 4. Token Claim Name: "groups"
// 5. Add to access token and ID token
```

### **2. Debug Commands**

```bash
# Check Keycloak connectivity
curl -X GET "https://auth.yourdomain.com/realms/your-realm/.well-known/openid_configuration"

# Test token endpoint directly
curl -X POST "https://auth.yourdomain.com/realms/your-realm/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=password&client_id=your-client&client_secret=your-secret&username=test&password=test123"

# Verify user info endpoint
curl -X GET "https://auth.yourdomain.com/realms/your-realm/protocol/openid-connect/userinfo" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

### **3. Common Configuration Mistakes**

❌ **Wrong Grant Type:**
```javascript
// WRONG - Don't use authorization code flow for custom login
standardFlowEnabled: true
directAccessGrantsEnabled: false
```

✅ **Correct Grant Type:**
```javascript
// CORRECT - Use Resource Owner Password Credentials
standardFlowEnabled: false
directAccessGrantsEnabled: true
```

❌ **Wrong Cookie Settings:**
```typescript
// WRONG - Insecure cookie settings
setCookie(event, 'session', sessionId, {
  httpOnly: false,  // ❌ XSS vulnerable
  secure: false,    // ❌ Not secure over HTTP
  sameSite: 'none'  // ❌ CSRF vulnerable
});
```

✅ **Correct Cookie Settings:**
```typescript
// CORRECT - Secure cookie settings
setCookie(event, 'session', sessionId, {
  httpOnly: true,           // ✅ XSS protection
  secure: isProduction,     // ✅ HTTPS in production
  sameSite: 'lax'          // ✅ CSRF protection
});
```

### **4. Performance Troubleshooting**

**Slow Login Response:**
- Check network latency to Keycloak server
- Verify Keycloak server performance
- Consider caching user info for short periods
- Optimize session creation process

**High Memory Usage:**
- Implement session cleanup for expired sessions
- Use external session storage (Redis) instead of memory
- Set appropriate session TTL values

**Database Connection Issues:**
- Configure proper connection pooling
- Set appropriate timeout values
- Implement retry logic for transient failures

## 📚 **Additional Resources**

### **Keycloak Documentation**
- [Resource Owner Password Credentials Flow](https://www.keycloak.org/docs/latest/securing_apps/#_resource_owner_password_credentials_flow)
- [Client Configuration](https://www.keycloak.org/docs/latest/server_admin/#_clients)
- [User Groups and Roles](https://www.keycloak.org/docs/latest/server_admin/#con-groups_server_administration_guide)

### **Security Best Practices**
- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html)
- [OWASP Session Management](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
- [Cookie Security Guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security)

### **Nuxt 3 Specific**
- [Nuxt 3 Authentication Patterns](https://nuxt.com/docs/guide/directory-structure/middleware#authentication)
- [Server Routes](https://nuxt.com/docs/guide/directory-structure/server)
- [Runtime Config](https://nuxt.com/docs/api/nuxt-config#runtimeconfig)

---

## 🎉 **Conclusion**

This comprehensive guide covers implementing a custom login page with Keycloak in a Nuxt 3 application. The solution provides:

✅ **Complete UI Control**: Custom branded login experience
✅ **Mobile Compatibility**: Works perfectly on all devices including iOS Safari
✅ **Enterprise Security**: Rate limiting, input validation, secure sessions
✅ **Production Ready**: Proper error handling, monitoring, and scalability
✅ **Maintainable**: Clean separation of concerns and well-documented code

The implementation balances security, performance, and user experience while providing a solid foundation for enterprise authentication needs.
