import { browser } from '$app/environment'; import { PUBLIC_KEYCLOAK_URL, PUBLIC_KEYCLOAK_REALM, PUBLIC_KEYCLOAK_CLIENT_ID } from '$env/static/public'; import { getSupabase } from '$lib/supabase'; /** * Set for the duration of the logout flow so the (app) layout's auth-gate * $effect doesn't race us by calling login() between signOut() and the * Keycloak end-session redirect. */ export let isLoggingOut = false; /** * Redirect to Keycloak login via GoTrue OAuth proxy. * GoTrue handles the PKCE exchange; the browser lands at /auth/callback. */ export async function login(redirectTo?: string): Promise { if (!browser) return; const { error } = await getSupabase().auth.signInWithOAuth({ provider: 'keycloak', options: { redirectTo: redirectTo ?? `${window.location.origin}/auth/callback`, scopes: 'openid profile email' } }); if (error) throw error; } /** * RP-initiated OIDC logout: clear the Supabase session AND end the Keycloak * SSO session, then land on a public /logged-out page. * * Why both: `supabase.auth.signOut()` only clears GoTrue state. If we don't * also hit Keycloak's end-session endpoint, the Keycloak SSO cookie survives * and the next `signInWithOAuth()` silently re-authenticates — making logout * look broken. It also eliminates a race where the (app) layout's auth * effect re-fires `login()` before navigation and can clobber the PKCE * verifier mid-flight ("PKCE code verifier not found in storage" on prod). * * Keycloak's `post.logout.redirect.uris` is set to "+" so any registered * redirect URI (including `${origin}/*`) is valid as a post-logout target; * passing `client_id` is sufficient — `id_token_hint` is not exposed by * GoTrue's proxy path, so we rely on client_id + post_logout_redirect_uri. */ export async function logout(): Promise { if (!browser) return; isLoggingOut = true; await getSupabase().auth.signOut(); const postLogoutRedirect = `${window.location.origin}/logged-out`; const endSession = new URL( `${PUBLIC_KEYCLOAK_URL}/realms/${PUBLIC_KEYCLOAK_REALM}/protocol/openid-connect/logout` ); endSession.searchParams.set('client_id', PUBLIC_KEYCLOAK_CLIENT_ID); endSession.searchParams.set('post_logout_redirect_uri', postLogoutRedirect); window.location.assign(endSession.toString()); }