import NextAuth, { Session } from "next-auth";
import { JWT } from "next-auth/jwt";
import Keycloak from "next-auth/providers/keycloak";
declare module "next-auth" {
interface User {
// Add your additional properties here:
}
interface Session {
idToken: string;
accessToken: string;
expiresAt: number;
user: User;
}
}
function refreshAccessToken(token: JWT) {
return fetch(
`${process.env.AUTH_KEYCLOAK_ISSUER}/protocol/openid-connect/token`,
{
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.AUTH_KEYCLOAK_ID ?? "",
client_secret: process.env.AUTH_KEYCLOAK_SECRET ?? "",
grant_type: "refresh_token",
refresh_token: token?.refreshToken as string,
}),
method: "POST",
cache: "no-store",
}
);
}
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [Keycloak],
callbacks: {
async jwt({ token, account }) {
if (account) {
token.idToken = account.id_token;
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
token.expiresAt = account.expires_at;
return token;
}
if (Date.now() < (token?.expiresAt as number) * 1000 - 60 * 1000) {
return token;
} else {
try {
const response = await refreshAccessToken(token);
const tokens = await response.json();
if (!response.ok) throw tokens;
const updatedToken: JWT = {
...token,
idToken: tokens.id_token,
accessToken: tokens.access_token,
expiresAt: Math.floor(
Date.now() / 1000 + (tokens.expires_in as number)
),
refreshToken: tokens.refresh_token ?? token.refreshToken,
};
return updatedToken;
} catch (error) {
console.error("Error refreshing access token", error);
// By returning null it looks like user is logged off
// Maybe push this way and logout keycloak session & redirect
return null;
// Or else you just return the session with the error to the frontend
// return { ...token, error: "RefreshAccessTokenError" }
}
}
},
async session({ session, token }: any) {
session.accessToken = token.accessToken;
session.idToken = token.idToken;
session.error = token.error;
return session;
},
},
});
export async function logout(session: Session | null) {
if (!session) {
return;
}
await fetch(
`${process.env.AUTH_KEYCLOAK_ISSUER}/protocol/openid-connect/logout?id_token_hint=${session.idToken}`,
{ method: "GET" }
);
await signOut();
}