156 lines
4.4 KiB
TypeScript
156 lines
4.4 KiB
TypeScript
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) {
|
|
// Store access token
|
|
token.accessToken = account.access_token;
|
|
token.refreshToken = account.refresh_token;
|
|
|
|
// Extract roles correctly from the raw Keycloak profile
|
|
if (profile.realm_access && profile.realm_access.roles) {
|
|
// Directly extract roles from the Keycloak profile structure
|
|
token.role = profile.realm_access.roles.map(
|
|
(role: string) => role.replace(/^ROLE_/, '').toLowerCase()
|
|
);
|
|
} else if (profile.role) {
|
|
// Fallback to using the role property if already processed
|
|
token.role = profile.role;
|
|
}
|
|
|
|
// Store user information
|
|
token.username = profile.preferred_username || profile.username || '';
|
|
token.first_name = profile.given_name || profile.first_name || '';
|
|
token.last_name = profile.family_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 && Array.isArray(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 = '';
|
|
}
|
|
|
|
// Add debug log to see what roles are being passed
|
|
console.log('Session user roles:', session.user.role);
|
|
}
|
|
return session;
|
|
}
|
|
},
|
|
pages: {
|
|
signIn: '/signin',
|
|
error: '/signin',
|
|
},
|
|
cookies: {
|
|
sessionToken: {
|
|
name: 'next-auth.session-token',
|
|
options: {
|
|
httpOnly: true,
|
|
sameSite: 'none',
|
|
path: '/',
|
|
secure: true,
|
|
},
|
|
},
|
|
},
|
|
debug: true, // Enable debug logs temporarily to see role information
|
|
};
|
|
|
|
const handler = NextAuth(authOptions);
|
|
export { handler as GET, handler as POST };
|
|
|