NeahFront9/lib/auth.ts

179 lines
5.1 KiB
TypeScript

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[];
first_name: string;
last_name: string;
username: string;
}
interface Session {
user: {
id: string;
email: string;
name?: string | null;
role: string[];
first_name: string;
last_name: string;
username: string;
};
accessToken: string;
refreshToken: string;
}
interface Profile {
sub?: string;
email?: string;
name?: string;
roles?: string[];
given_name?: string;
family_name?: string;
preferred_username?: string;
}
}
declare module 'next-auth/jwt' {
interface JWT {
id: string;
email: string;
name?: string;
role: string[];
first_name: string;
last_name: string;
username: 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,
authorization: {
params: {
scope: 'openid email profile',
response_type: 'code',
}
},
}),
],
debug: true,
session: {
strategy: 'jwt',
maxAge: 30 * 24 * 60 * 60, // 30 days
},
pages: {
signIn: '/signin',
error: '/signin',
},
callbacks: {
async jwt({ token, account, profile }) {
console.log('JWT callback:', {
tokenBefore: { ...token, refreshToken: token.refreshToken ? '[REDACTED]' : undefined },
account: account ? { ...account, refresh_token: '[REDACTED]' } : null,
profile
});
if (account && profile) {
if (!profile.sub) {
console.error('No user ID (sub) provided by Keycloak');
throw new Error('No user ID (sub) provided by Keycloak');
}
if (!account.access_token || !account.refresh_token || !account.expires_at) {
console.error('Missing required token fields from Keycloak');
throw new Error('Missing required token fields from Keycloak');
}
token.id = profile.sub;
token.email = profile.email || '';
token.name = profile.name;
token.role = profile.roles || ['user'];
token.first_name = profile.given_name || '';
token.last_name = profile.family_name || '';
token.username = profile.preferred_username || '';
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
token.accessTokenExpires = account.expires_at * 1000;
console.log('JWT token updated:', {
tokenAfter: { ...token, refreshToken: '[REDACTED]' }
});
}
// Return previous token if not expired
if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) {
return token;
}
// Token expired, try to refresh
if (!token.refreshToken) {
console.error('No refresh token available');
throw new Error('No refresh token available');
}
try {
console.log('Attempting to refresh token...');
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,
}),
}
);
const tokens = await response.json();
if (!response.ok) {
console.error('Token refresh failed:', tokens);
throw new Error('RefreshAccessTokenError');
}
console.log('Token refreshed successfully');
return {
...token,
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token ?? token.refreshToken,
accessTokenExpires: Date.now() + tokens.expires_in * 1000,
};
} catch (error) {
console.error('Error refreshing token:', 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;
session.user.first_name = token.first_name;
session.user.last_name = token.last_name;
session.user.username = token.username;
session.accessToken = token.accessToken;
session.refreshToken = token.refreshToken;
return session;
},
},
};