diff --git a/internal_frontend/app/api/auth/[...nextauth]/route.ts b/internal_frontend/app/api/auth/[...nextauth]/route.ts index 2652a29..fe6ebe0 100644 --- a/internal_frontend/app/api/auth/[...nextauth]/route.ts +++ b/internal_frontend/app/api/auth/[...nextauth]/route.ts @@ -1,92 +1,5 @@ import NextAuth from "next-auth"; -import KeycloakProvider from "next-auth/providers/keycloak"; -import type {NextAuthOptions} from "next-auth"; - -interface TypedJWT { - access_token?: string; - refresh_token?: string; - - [key: string]: unknown; -} - -const { - KEYCLOAK_CLIENT_ID, - KEYCLOAK_CLIENT_SECRET, - KEYCLOAK_ISSUER, - NEXTAUTH_SECRET, -} = process.env; - -if (!KEYCLOAK_CLIENT_ID) throw new Error("Missing KEYCLOAK_CLIENT_ID"); -if (!KEYCLOAK_CLIENT_SECRET) throw new Error("Missing KEYCLOAK_CLIENT_SECRET"); -if (!KEYCLOAK_ISSUER) throw new Error("Missing KEYCLOAK_ISSUER"); -if (!NEXTAUTH_SECRET) throw new Error("Missing NEXTAUTH_SECRET"); - -console.log("[auth] Using Keycloak provider:"); -console.log(" - Client ID:", KEYCLOAK_CLIENT_ID); -console.log(" - Issuer:", KEYCLOAK_ISSUER); - -async function isTokenValid(token: string): Promise { - try { - const res = await fetch(`${KEYCLOAK_ISSUER}/protocol/openid-connect/userinfo`, { - headers: { - Authorization: `Bearer ${token}`, - }, - }); - - return res.ok; - } catch (error) { - console.error("[auth] Failed to validate access token:", error); - return false; - } -} - -export const authOptions: NextAuthOptions = { - providers: [ - KeycloakProvider({ - clientId: KEYCLOAK_CLIENT_ID, - clientSecret: KEYCLOAK_CLIENT_SECRET, - issuer: KEYCLOAK_ISSUER, - }), - ], - secret: NEXTAUTH_SECRET, - session: { - strategy: "jwt", - }, - callbacks: { - async jwt({token, account}) { - if (account) { - token.access_token = account.access_token; - token.refresh_token = account.refresh_token; - console.log("[auth] JWT callback: new login from Keycloak"); - return token; - } - - const {access_token} = token as TypedJWT; - if (access_token) { - const valid = await isTokenValid(access_token); - if (!valid) { - console.warn("[auth] Access token invalid — clearing session"); - return {}; - } - } - - console.log("[auth] JWT callback: reusing existing token"); - return token; - }, - - async session({session, token}) { - const {access_token, refresh_token} = token as TypedJWT; - console.log("[auth] Session callback: enriching session with tokens"); - return { - ...session, - accessToken: access_token, - refreshToken: refresh_token, - }; - }, - }, -}; - -console.log("[auth] NextAuth handler initialized"); +import {authOptions} from "@/lib/auth/authOptions"; const handler = NextAuth(authOptions); export {handler as GET, handler as POST}; diff --git a/internal_frontend/lib/auth/authOptions.ts b/internal_frontend/lib/auth/authOptions.ts new file mode 100644 index 0000000..a8bb1eb --- /dev/null +++ b/internal_frontend/lib/auth/authOptions.ts @@ -0,0 +1,86 @@ +import KeycloakProvider from "next-auth/providers/keycloak"; +import type {NextAuthOptions} from "next-auth"; + +interface TypedJWT { + access_token?: string; + refresh_token?: string; + + [key: string]: unknown; +} + +const { + KEYCLOAK_CLIENT_ID, + KEYCLOAK_CLIENT_SECRET, + KEYCLOAK_ISSUER, + NEXTAUTH_SECRET, +} = process.env; + +if (!KEYCLOAK_CLIENT_ID) throw new Error("Missing KEYCLOAK_CLIENT_ID"); +if (!KEYCLOAK_CLIENT_SECRET) throw new Error("Missing KEYCLOAK_CLIENT_SECRET"); +if (!KEYCLOAK_ISSUER) throw new Error("Missing KEYCLOAK_ISSUER"); +if (!NEXTAUTH_SECRET) throw new Error("Missing NEXTAUTH_SECRET"); + +console.log("[auth] Using Keycloak provider:"); +console.log(" - Client ID:", KEYCLOAK_CLIENT_ID); +console.log(" - Issuer:", KEYCLOAK_ISSUER); + +async function isTokenValid(token: string): Promise { + try { + const res = await fetch(`${KEYCLOAK_ISSUER}/protocol/openid-connect/userinfo`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + return res.ok; + } catch (error) { + console.error("[auth] Failed to validate access token:", error); + return false; + } +} + +export const authOptions: NextAuthOptions = { + providers: [ + KeycloakProvider({ + clientId: KEYCLOAK_CLIENT_ID, + clientSecret: KEYCLOAK_CLIENT_SECRET, + issuer: KEYCLOAK_ISSUER, + }), + ], + secret: NEXTAUTH_SECRET, + session: { + strategy: "jwt", + }, + callbacks: { + async jwt({token, account}) { + if (account) { + token.access_token = account.access_token; + token.refresh_token = account.refresh_token; + console.log("[auth] JWT callback: new login from Keycloak"); + return token; + } + + const {access_token} = token as TypedJWT; + if (access_token) { + const valid = await isTokenValid(access_token); + if (!valid) { + console.warn("[auth] Access token invalid — clearing session"); + return {}; + } + } + + console.log("[auth] JWT callback: reusing existing token"); + return token; + }, + + async session({session, token}) { + const {access_token, refresh_token} = token as TypedJWT; + console.log("[auth] Session callback: enriching session with tokens"); + return { + ...session, + accessToken: access_token, + refreshToken: refresh_token, + }; + }, + }, +}; \ No newline at end of file