import { NextAuthOptions } from 'next-auth'; import KeycloakProvider from 'next-auth/providers/keycloak'; declare module 'next-auth' { interface User { id: string; email: string; name?: string | null; role: string[]; } interface Session { user: { id: string; email: string; name?: string | null; role: string[]; }; } interface Profile { sub: string; email?: string; name?: string; roles?: string[]; } } declare module 'next-auth/jwt' { interface JWT { id: string; email: string; name?: string; role: string[]; accessToken?: string; refreshToken?: string; accessTokenExpires?: number; error?: string; } } export const authOptions: NextAuthOptions = { providers: [ KeycloakProvider({ clientId: process.env.KEYCLOAK_CLIENT_ID!, clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!, issuer: process.env.KEYCLOAK_ISSUER, }), ], session: { strategy: 'jwt', maxAge: 30 * 24 * 60 * 60, // 30 days }, pages: { signIn: '/signin', error: '/signin', }, callbacks: { async jwt({ token, account, profile }) { if (account && profile) { token.id = profile.sub; token.email = profile.email || ''; token.name = profile.name; token.role = profile.roles || ['user']; token.accessToken = account.access_token; token.refreshToken = account.refresh_token; token.accessTokenExpires = account.expires_at! * 1000; } // Return previous token if not expired if (Date.now() < (token.accessTokenExpires as number)) { return token; } // Token expired, try to refresh 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 tokens = await response.json(); if (!response.ok) { throw new Error('RefreshAccessTokenError'); } return { ...token, accessToken: tokens.access_token, refreshToken: tokens.refresh_token ?? token.refreshToken, accessTokenExpires: Date.now() + tokens.expires_in * 1000, }; } catch (error) { return { ...token, error: 'RefreshAccessTokenError', }; } }, async session({ session, token }) { if (token.error) { throw new Error('RefreshAccessTokenError'); } session.user.id = token.id; session.user.email = token.email; session.user.name = token.name; session.user.role = token.role; return session; }, }, };