import NextAuth, { NextAuthOptions } from "next-auth"; import KeycloakProvider from "next-auth/providers/keycloak"; export const authOptions: NextAuthOptions = { providers: [ KeycloakProvider({ clientId: process.env.KEYCLOAK_CLIENT_ID!, clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!, issuer: process.env.KEYCLOAK_ISSUER!, // Personnalisation de la fonction profile pour inclure le rôle de Keycloak profile(profileData) { return { id: profileData.sub, first_name: profileData.given_name, last_name: profileData.family_name, username: profileData.preferred_username, email: profileData.email, role: profileData.realm_roles, }; }, }), ], callbacks: { async jwt({ token, account, profile }) { // Au moment de la première connexion, sauvegarde de l'access token et du rôle dans le JWT if (account && profile) { token.accessToken = account.access_token; token.refreshToken = account.refresh_token; token.accessTokenExpires = account.expires_at! * 1000; token.first_name = profile.given_name; token.last_name = profile.family_name; token.username = profile.preferred_username; token.role = profile.realm_roles; return token; } // Retourner le token précédent si le token d'accès n'a pas expiré if (Date.now() < (token.accessTokenExpires as number)) { return token; } // Rafraîchir le token si expiré try { const response = await fetch( `${process.env.KEYCLOAK_BASE_URL}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", client_id: process.env.KEYCLOAK_CLIENT_ID!, client_secret: process.env.KEYCLOAK_CLIENT_SECRET!, refresh_token: token.refreshToken as string, }), } ); const refreshedTokens = await response.json(); if (!response.ok) { throw refreshedTokens; } return { ...token, accessToken: refreshedTokens.access_token, refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000, }; } catch (error) { console.error("Erreur lors du rafraîchissement du token:", error); return { ...token, error: "RefreshAccessTokenError", }; } }, async session({ session, token }) { if (token.error) { // Rediriger vers la page de connexion si le rafraîchissement a échoué throw new Error("RefreshAccessTokenError"); } // On injecte l'access token et le rôle dans la session accessible côté client session.accessToken = token.accessToken as string; session.refreshToken = token.refreshToken as string; session.error = token.error as string; session.user.first_name = token.first_name as string; session.user.last_name = token.last_name as string; session.user.username = token.username as string; session.user.role = token.role as string[]; return session; }, }, session: { strategy: "jwt", maxAge: 60 * 60, // 1 heure }, events: { async signOut({ token }) { try { const issuerUrl = process.env.KEYCLOAK_ISSUER!; const logoutUrl = `${issuerUrl}/protocol/openid-connect/logout`; await fetch(logoutUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ client_id: process.env.KEYCLOAK_CLIENT_ID!, client_secret: process.env.KEYCLOAK_CLIENT_SECRET!, refresh_token: token.refreshToken as string, }), }); } catch (error) { console.error("Erreur lors de la déconnexion Keycloak:", error); } }, }, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };