/** * @jest-environment node */ import { NextRequest } from "next/server"; import { getToken } from "next-auth/jwt"; import { GET } from "./route"; jest.mock("next-auth/jwt", () => ({ getToken: jest.fn(), })); const getTokenMock = getToken as jest.MockedFunction; describe("GET /api/auth/keycloak-logout", () => { const originalIssuer = process.env.KEYCLOAK_ISSUER; const originalClientId = process.env.KEYCLOAK_CLIENT_ID; const originalNextAuthUrl = process.env.NEXTAUTH_URL; const originalPostLogoutRedirectUri = process.env.KEYCLOAK_POST_LOGOUT_REDIRECT_URI; beforeEach(() => { process.env.KEYCLOAK_ISSUER = "https://keycloak.example.com/realms/tjwater"; process.env.KEYCLOAK_CLIENT_ID = "tjwater"; process.env.NEXTAUTH_URL = "https://frontend.example.com"; delete process.env.KEYCLOAK_POST_LOGOUT_REDIRECT_URI; getTokenMock.mockReset(); }); afterAll(() => { process.env.KEYCLOAK_ISSUER = originalIssuer; process.env.KEYCLOAK_CLIENT_ID = originalClientId; process.env.NEXTAUTH_URL = originalNextAuthUrl; if (originalPostLogoutRedirectUri) { process.env.KEYCLOAK_POST_LOGOUT_REDIRECT_URI = originalPostLogoutRedirectUri; } else { delete process.env.KEYCLOAK_POST_LOGOUT_REDIRECT_URI; } }); it("clears the local session and redirects the browser to Keycloak logout", async () => { getTokenMock.mockResolvedValue({ idToken: "header.payload.signature", }); const request = new NextRequest( "https://frontend.example.com/api/auth/keycloak-logout", { headers: { cookie: "__Secure-next-auth.session-token.0=first; __Secure-next-auth.session-token.1=second; next-auth.session-token=local", }, }, ); const response = await GET(request); const logoutUrl = new URL(response.headers.get("location") ?? ""); expect(logoutUrl.origin).toBe("https://keycloak.example.com"); expect(logoutUrl.pathname).toBe( "/realms/tjwater/protocol/openid-connect/logout", ); expect(logoutUrl.searchParams.get("id_token_hint")).toBe( "header.payload.signature", ); expect(logoutUrl.searchParams.get("client_id")).toBe("tjwater"); expect(logoutUrl.searchParams.get("post_logout_redirect_uri")).toBeNull(); expect( response.cookies.get("__Secure-next-auth.session-token.0"), ).toMatchObject({ value: "", path: "/", secure: true }); expect( response.cookies.get("__Secure-next-auth.session-token.1"), ).toMatchObject({ value: "", path: "/", secure: true }); expect(response.cookies.get("next-auth.session-token")).toMatchObject({ value: "", path: "/", }); expect( response.cookies.get("next-auth.session-token")?.secure, ).toBeFalsy(); }); it("ignores a legacy automatic post-logout redirect setting", async () => { process.env.KEYCLOAK_POST_LOGOUT_REDIRECT_URI = "https://frontend.example.com/login"; getTokenMock.mockResolvedValue({ idToken: "header.payload.signature" }); const response = await GET( new NextRequest( "https://frontend.example.com/api/auth/keycloak-logout", ), ); const logoutUrl = new URL(response.headers.get("location") ?? ""); expect(logoutUrl.searchParams.get("post_logout_redirect_uri")).toBeNull(); }); });