feat: Update authentication flow to unify logout process and improve Keycloak integration

This commit is contained in:
2026-04-24 12:09:29 -05:00
parent 5ac757dbc1
commit 225f020747
3 changed files with 16 additions and 24 deletions

View File

@@ -113,6 +113,9 @@ services:
- NODE_ENV=${NODE_ENV:-development}
- VITE_API_URL=${VITE_API_URL:-http://localhost:8000/api/}
- INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/}
- VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-http://localhost:8085/kcauth}
- VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master}
- VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend}
# Hub — URL pública para el browser y URL interna para server-side
- VITE_HUB_URL=${VITE_HUB_URL:-http://localhost:8001}
- INTERNAL_HUB_URL=${INTERNAL_HUB_URL:-http://host.docker.internal:8001}

View File

@@ -370,18 +370,14 @@ export const logout = async () => {
deleteCookie('access_token');
// La cookie HttpOnly del refresh_token la limpia el servidor
// Logout de Keycloak JS si estaba autenticado con SSO
if (keycloakInstance?.authenticated) {
// Logout unificado (SSO y password): POST al logout route del servidor.
// Evita redirección visible al endpoint de Keycloak.
if (keycloakInstance) {
try {
await fetch('/logout', { method: 'POST' });
keycloakInstance.clearToken();
} catch {}
await keycloakInstance.logout({
redirectUri: window.location.origin + '/login'
});
return;
}
// Para login con password: POST al logout route del servidor
const form = document.createElement('form');
form.method = 'POST';
form.action = '/logout';

View File

@@ -4,6 +4,8 @@ import type { RequestHandler } from './$types';
export const POST: RequestHandler = async ({ cookies, request }) => {
const refreshToken = cookies.get('refresh_token');
const appOrigin = new URL(request.url).origin;
const loginUrl = `${appOrigin}/login`;
// Eliminar todas las cookies de autenticación
cookies.delete('access_token', { path: '/' });
@@ -12,38 +14,29 @@ export const POST: RequestHandler = async ({ cookies, request }) => {
cookies.delete('sso_tenant_id', { path: '/' });
cookies.delete('sso_tenant_pub', { path: '/' });
// Llamar al Hub para revocar el refresh token y obtener la URL de logout KC.
// Si lo logramos, redirigimos al browser a través del endpoint KC logout para
// que Keycloak elimine su cookie de sesión SSO (evita auto-login silencioso).
// Llamar al Hub para revocar el refresh token.
// La navegación final siempre debe volver al login local de anexo76
// sin redirigir al endpoint de logout de Keycloak.
if (refreshToken) {
try {
const hubUrl = (env.INTERNAL_HUB_URL || env.HUB_URL || 'http://localhost:8001').replace(/\/+$/, '');
// Detectar el origen del request para usar como post_logout_redirect_uri
const origin = request.headers.get('origin') || request.headers.get('referer')?.replace(/\/$/, '') || '';
const postLogoutUri = origin ? `${origin}/login` : '/login';
const res = await fetch(`${hubUrl}/api/v1/auth/logout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
refresh_token: refreshToken,
post_logout_redirect_uri: postLogoutUri,
post_logout_redirect_uri: loginUrl,
}),
});
if (res.ok) {
const data = await res.json().catch(() => ({}));
if (data.kc_logout_url) {
// Redirigir el browser al endpoint KC logout para limpiar la sesión SSO
throw redirect(303, data.kc_logout_url);
}
await res.json().catch(() => ({}));
}
} catch (err: any) {
// Si es un redirect de SvelteKit, relanzar
if (err?.status && err?.location) throw err;
// Si falla, caer al login local
// Si falla la llamada al Hub, caer al login local
}
}
throw redirect(303, '/login');
throw redirect(303, loginUrl);
};