import NextAuth, { NextAuthOptions } from "next-auth"; import KeycloakProvider from "next-auth/providers/keycloak"; // Define Keycloak profile type interface KeycloakProfile { sub: string; email?: string; name?: string; preferred_username?: string; given_name?: string; family_name?: string; realm_access?: { roles: string[]; }; } // Define custom profile type interface CustomProfile { id: string; name?: string | null; email?: string | null; username: string; first_name: string; last_name: string; role: string[]; } // Declare module augmentation for NextAuth types declare module "next-auth" { interface Session { user: { id: string; name?: string | null; email?: string | null; image?: string | null; username: string; first_name: string; last_name: string; role: string[]; }; accessToken?: string; } interface JWT { sub?: string; accessToken?: string; refreshToken?: string; role?: string[]; username?: string; first_name?: string; last_name?: string; } } // Simple, minimal implementation - NO REFRESH TOKEN LOGIC export const authOptions: NextAuthOptions = { providers: [ KeycloakProvider({ clientId: process.env.KEYCLOAK_CLIENT_ID || "", clientSecret: process.env.KEYCLOAK_CLIENT_SECRET || "", issuer: process.env.KEYCLOAK_ISSUER || "", profile(profile: any) { // Extract roles from the profile const roles = profile.realm_access?.roles || []; return { id: profile.sub, name: profile.name || profile.preferred_username, email: profile.email, image: null, role: roles.map((role: string) => role.replace(/^ROLE_/, '').toLowerCase()), first_name: profile.given_name || '', last_name: profile.family_name || '', username: profile.preferred_username || profile.email?.split('@')[0] || '', }; } }), ], session: { strategy: "jwt", maxAge: 8 * 60 * 60, // 8 hours }, callbacks: { async jwt({ token, account, profile }: any) { if (account && profile) { // Just store access token and critical fields token.accessToken = account.access_token; // Make sure roles are available if (profile.role) { token.role = profile.role; token.username = profile.username || ''; token.first_name = profile.first_name || ''; token.last_name = profile.last_name || ''; } } return token; }, async session({ session, token }: any) { // Pass necessary info to the session session.accessToken = token.accessToken; if (session.user) { session.user.id = token.sub || ""; // Ensure roles are passed to the session if (token.role) { session.user.role = token.role; session.user.username = token.username || ''; session.user.first_name = token.first_name || ''; session.user.last_name = token.last_name || ''; } else { // Fallback roles session.user.role = ["user"]; session.user.username = ''; session.user.first_name = ''; session.user.last_name = ''; } } return session; } }, pages: { signIn: '/signin', error: '/signin', }, cookies: { sessionToken: { name: 'next-auth.session-token', options: { httpOnly: true, sameSite: 'none', path: '/', secure: true, }, }, }, debug: false, }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };