import { NextRequest, NextResponse } from 'next/server'; import { createHash, randomBytes } from 'crypto'; /** * Génère un code verifier et challenge pour PKCE */ function generatePKCE() { // Générer un code verifier aléatoire const codeVerifier = randomBytes(32).toString('base64url'); // Générer le code challenge (SHA256 du verifier) const codeChallenge = createHash('sha256') .update(codeVerifier) .digest('base64url'); return { codeVerifier, codeChallenge }; } /** * API Route pour déclencher l'authentification Keycloak * Redirige directement vers Keycloak sans page intermédiaire */ export async function GET(request: NextRequest) { try { // Configuration Keycloak depuis les variables d'environnement const keycloakUrl = process.env.NEXT_PUBLIC_KEYCLOAK_URL || 'https://security.lions.dev'; const realm = process.env.NEXT_PUBLIC_KEYCLOAK_REALM || 'btpxpress'; const clientId = process.env.NEXT_PUBLIC_KEYCLOAK_CLIENT_ID || 'btpxpress-frontend'; // URL de redirection après authentification (vers la page dashboard) // Utiliser une URL fixe pour éviter les problèmes avec request.nextUrl.origin const baseUrl = process.env.NODE_ENV === 'production' ? 'https://btpxpress.lions.dev' : 'http://localhost:3000'; const redirectUri = encodeURIComponent(`${baseUrl}/dashboard`); // Toujours utiliser l'URI de redirection simple vers /dashboard // Ne pas utiliser le paramètre redirect qui peut contenir des codes d'autorisation obsolètes const finalRedirectUri = redirectUri; // Générer les paramètres PKCE const { codeVerifier, codeChallenge } = generatePKCE(); // Construire l'URL d'authentification Keycloak const authUrl = new URL(`${keycloakUrl}/realms/${realm}/protocol/openid-connect/auth`); authUrl.searchParams.set('client_id', clientId); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('redirect_uri', decodeURIComponent(finalRedirectUri)); authUrl.searchParams.set('scope', 'openid profile email'); authUrl.searchParams.set('code_challenge', codeChallenge); authUrl.searchParams.set('code_challenge_method', 'S256'); // Générer un state pour la sécurité (optionnel mais recommandé) const state = Math.random().toString(36).substring(2, 15); authUrl.searchParams.set('state', state); // Nettoyer les anciens cookies d'authentification const response = NextResponse.redirect(authUrl.toString()); // Supprimer les anciens cookies qui pourraient causer des conflits response.cookies.delete('keycloak-token'); response.cookies.delete('auth-state'); response.cookies.delete('pkce_code_verifier'); // Stocker le nouveau code verifier console.log('🍪 Création du cookie pkce_code_verifier:', { codeVerifier: codeVerifier.substring(0, 20) + '...', httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 1800 // 30 minutes }); response.cookies.set('pkce_code_verifier', codeVerifier, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', maxAge: 1800 // 30 minutes - plus de temps pour l'authentification }); // Redirection vers Keycloak return response; } catch (error) { console.error('Erreur lors de la redirection vers Keycloak:', error); // En cas d'erreur, retourner une erreur JSON return NextResponse.json( { error: 'Erreur lors de l\'initialisation de l\'authentification' }, { status: 500 } ); } } /** * Gestion des autres méthodes HTTP (non supportées) */ export async function POST() { return NextResponse.json( { error: 'Méthode non supportée. Utilisez GET pour déclencher l\'authentification.' }, { status: 405 } ); }