/** * Middleware Next.js pour l'authentification OAuth avec Keycloak * * Ce middleware protège les routes privées en vérifiant la présence * d'un access_token dans les cookies HttpOnly. */ import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; // Routes publiques accessibles sans authentification const PUBLIC_ROUTES = [ '/', '/auth/login', '/auth/callback', '/api/auth/login', '/api/auth/callback', '/api/auth/token', '/api/health', ]; // Routes API publiques (patterns) const PUBLIC_API_PATTERNS = [ /^\/api\/auth\/.*/, /^\/api\/health/, ]; // Vérifie si une route est publique function isPublicRoute(pathname: string): boolean { // Vérifier les routes exactes if (PUBLIC_ROUTES.includes(pathname)) { return true; } // Vérifier les patterns return PUBLIC_API_PATTERNS.some(pattern => pattern.test(pathname)); } export function middleware(request: NextRequest) { const { pathname } = request.nextUrl; console.log('🔒 Middleware - Checking:', pathname); // Laisser passer les routes publiques if (isPublicRoute(pathname)) { console.log('✅ Middleware - Public route, allowing'); return NextResponse.next(); } // Laisser passer les fichiers statiques if ( pathname.startsWith('/_next') || pathname.startsWith('/static') || pathname.includes('.') ) { return NextResponse.next(); } // Vérifier la présence du token d'authentification const accessToken = request.cookies.get('access_token'); const tokenExpiresAt = request.cookies.get('token_expires_at'); if (!accessToken) { console.log('❌ Middleware - No access token, redirecting to login'); // Rediriger vers la page de login avec l'URL de retour const loginUrl = new URL('/auth/login', request.url); loginUrl.searchParams.set('returnUrl', pathname); return NextResponse.redirect(loginUrl); } // Vérifier si le token est expiré if (tokenExpiresAt) { const expiresAt = parseInt(tokenExpiresAt.value, 10); const now = Date.now(); if (now >= expiresAt) { console.log('❌ Middleware - Token expired, redirecting to login'); // Supprimer les cookies expirés const response = NextResponse.redirect(new URL('/auth/login', request.url)); response.cookies.delete('access_token'); response.cookies.delete('refresh_token'); response.cookies.delete('id_token'); response.cookies.delete('token_expires_at'); return response; } } console.log('✅ Middleware - Authenticated, allowing'); // L'utilisateur est authentifié, laisser passer return NextResponse.next(); } // Configuration du matcher - appliqué à toutes les routes sauf les fichiers statiques export const config = { matcher: [ /* * Match all request paths except for the ones starting with: * - _next/static (static files) * - _next/image (image optimization files) * - favicon.ico (favicon file) * - public files (images, etc.) */ '/((?!_next/static|_next/image|favicon.ico|.*\\..*|public).*)', ], };