diff --git a/public/keycloak.json b/public/keycloak.json index 01679d8..77f770e 100644 --- a/public/keycloak.json +++ b/public/keycloak.json @@ -1,5 +1,5 @@ { - "realm": "angular-app", + "realm": "aangular-app", "auth-server-url": "http://localhost:8080/", "resource": "angular-app", "credentials": { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a3f0130..fea08b4 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -35,34 +35,42 @@ export class AppComponent implements OnInit, OnDestroy { // Authentication success handling if (event.type === KeycloakEventType.AuthSuccess) { - console.log('Authentication successful, redirecting to home'); - this.router.navigate(['/inicio']); + console.log('Authentication successful'); + // We'll let the guards and login component handle redirections + // No redirect here to avoid conflicts } // Authentication error handling if (event.type === KeycloakEventType.AuthError) { - console.error('Authentication error, redirecting to login'); - this.router.navigate(['/login']); + console.error('Authentication error'); + // Only redirect to login if not already there + if (this.router.url !== '/login') { + this.router.navigate(['/login']); + } } // Logout handling if (event.type === KeycloakEventType.AuthLogout) { - console.log('Logged out, redirecting to login'); - this.router.navigate(['/login']); + console.log('Logged out'); + // Only redirect to login if not already there + if (this.router.url !== '/login') { + this.router.navigate(['/login']); + } } }); } ngOnInit() { - // Subscribe to navigation events - this.router.events - .pipe( - filter(event => event instanceof NavigationEnd), - takeUntilDestroyed() - ) - .subscribe(() => { - console.log('Navigation completed to:', this.router.url); - }); + // Subscribe to navigation events - using standard unsubscribe pattern + this.subscriptions.push( + this.router.events + .pipe( + filter(event => event instanceof NavigationEnd) + ) + .subscribe(() => { + console.log('Navigation completed to:', this.router.url); + }) + ); // Check authentication status on load this.checkAuthenticationStatus(); @@ -78,25 +86,20 @@ export class AppComponent implements OnInit, OnDestroy { const isLoggedIn = await this.keycloak.authenticated; console.log('Initial authentication status:', isLoggedIn ? 'Authenticated' : 'Not authenticated'); - if (isLoggedIn) { - // Already authenticated, check if on protected route - if (this.router.url === '/login') { - this.router.navigate(['/inicio']); - } - } else { - // Not authenticated but trying to access a protected route - const isPublicRoute = this.router.url === '/login'; - - if (!isPublicRoute) { - console.log('User not authenticated, redirecting to Keycloak login'); - // Store current URL for redirect after login - const currentUrl = this.router.url; - - // Start Keycloak login flow - this.keycloak.login({ - redirectUri: window.location.origin + currentUrl - }); - } + // Let the guards handle the protected routes + // Only do minimal checks here to avoid redirect loops + + // If the user is on login page but already authenticated, send to home + if (isLoggedIn && this.router.url === '/login') { + console.log('Already authenticated, redirecting from login to home'); + this.router.navigate(['/inicio']); + return; + } + + // If not authenticated and on a protected route, go to Keycloak login + if (!isLoggedIn && this.router.url !== '/login') { + // We'll let the auth guard handle this + console.log('Not authenticated on protected route'); } } catch (error) { console.error('Error checking authentication status:', error); diff --git a/src/app/app.config.ts b/src/app/app.config.ts index 634af44..2a8bdab 100644 --- a/src/app/app.config.ts +++ b/src/app/app.config.ts @@ -51,15 +51,16 @@ export const appConfig: ApplicationConfig = { // Provide Keycloak with initOptions - this automatically creates an APP_INITIALIZER provideKeycloak({ config: { - url: 'http://localhost:8080', - realm: 'angular-app', - clientId: 'angular-app' + url: 'http://localhost:8080', // Asegúrate de que esta URL coincida con tu servidor Keycloak + realm: 'angular-app', // Asegúrate de que este realm existe en tu servidor Keycloak + clientId: 'angular-app', }, initOptions: { - onLoad: 'check-sso', + onLoad: 'check-sso', // Cambiado de check-sso a login-required para forzar login silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`, checkLoginIframe: false, - pkceMethod: 'S256' // Added for better security + pkceMethod: 'S256', + enableLogging: true }, // Configure the interceptor by providing the URLs where to include the token providers: [ diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts index 2e40cd6..eafe206 100644 --- a/src/app/guards/auth.guard.ts +++ b/src/app/guards/auth.guard.ts @@ -4,7 +4,7 @@ import Keycloak from 'keycloak-js'; import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; import { createAuthGuard, AuthGuardData } from 'keycloak-angular'; -// This is a simplified direct implementation +// Simple implementation for authentication guard export const authGuard: CanActivateFn = async ( route: ActivatedRouteSnapshot, state: RouterStateSnapshot @@ -12,28 +12,24 @@ export const authGuard: CanActivateFn = async ( const keycloak = inject(Keycloak); const router = inject(Router); - // Check if user is authenticated - const authenticated = await keycloak.authenticated; - - if (authenticated) { - return true; - } - - // If not authenticated, redirect to Keycloak login - console.log('User not authenticated, redirecting to Keycloak login'); - - // Store the URL the user was trying to access - const returnUrl = state.url; - try { - await keycloak.login({ - redirectUri: window.location.origin + returnUrl + // Check if user is authenticated + const authenticated = await keycloak.authenticated; + + if (authenticated) { + console.log('User is authenticated, allowing access to protected route'); + return true; + } + + // If not authenticated, redirect to login + console.log('User not authenticated, redirecting to login page'); + return router.createUrlTree(['/login'], { + queryParams: { returnUrl: state.url !== '/' ? state.url : '/inicio' } }); - return false; } catch (error) { - console.error('Error during login redirect:', error); - // Fallback to local login component if Keycloak login fails - return router.createUrlTree(['/login'], { queryParams: { returnUrl } }); + console.error('Error checking authentication:', error); + // Fallback to login on error + return router.createUrlTree(['/login']); } }; diff --git a/src/app/pages/login/login.component.ts b/src/app/pages/login/login.component.ts index 6fd4388..ddc46e6 100644 --- a/src/app/pages/login/login.component.ts +++ b/src/app/pages/login/login.component.ts @@ -12,6 +12,8 @@ import { ToastModule } from 'primeng/toast'; import { MessageService } from 'primeng/api'; import { FooterComponent } from "../../components/footer/footer.component"; import { AuthService } from '../../services/auth.service'; +import { take } from 'rxjs/operators'; +import { firstValueFrom } from 'rxjs'; @Component({ selector: 'app-login', @@ -43,16 +45,22 @@ export class LoginComponent implements OnInit { constructor(private messageService: MessageService) {} - async ngOnInit() { + ngOnInit() { // Obtener el returnUrl de los query params si existe this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/inicio'; - // Verificar si ya está autenticado y redirigir - this.authService.isLoggedIn().subscribe(isLoggedIn => { - if (isLoggedIn) { - console.log('Usuario ya autenticado, redirigiendo...'); - this.router.navigateByUrl(this.returnUrl); - } + // Simplificación - verificar autenticación sin promesas anidadas + this.authService.isLoggedIn().subscribe({ + next: (isLoggedIn) => { + if (isLoggedIn) { + console.log('Usuario ya autenticado, redirigiendo a:', this.returnUrl); + // Verificar si la URL de retorno es válida + const effectiveReturnUrl = this.returnUrl === '/' ? '/inicio' : this.returnUrl; + // Redirigir + this.router.navigate([effectiveReturnUrl], { replaceUrl: true }); + } + }, + error: (error) => console.error('Error al verificar autenticación:', error) }); } @@ -61,12 +69,16 @@ export class LoginComponent implements OnInit { this.loading = true; try { - // Construir el redirectUri para que vuelva a la URL que estaba intentando acceder - const redirectUri = window.location.origin + this.returnUrl; + // Verificar si la URL de retorno es válida + const effectiveReturnUrl = this.returnUrl === '/' ? '/inicio' : this.returnUrl; + + // Construir el redirectUri + const redirectUri = window.location.origin + effectiveReturnUrl; console.log('Iniciando login con redirectUri:', redirectUri); + // Iniciar el flujo de autenticación de Keycloak await this.authService.login(redirectUri); - // No es necesario manejar el redirect aquí, Keycloak lo hará automáticamente + // Keycloak se encargará de la redirección } catch (error) { console.error('Error al iniciar sesión:', error); this.loading = false; diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index 032cc44..f600135 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -4,7 +4,7 @@ import { BehaviorSubject, Observable, from } from 'rxjs'; import { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType } from 'keycloak-angular'; import { Router } from '@angular/router'; import Keycloak from 'keycloak-js'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MessageService } from 'primeng/api'; @Injectable({ providedIn: 'root' @@ -14,6 +14,7 @@ export class AuthService { private keycloak = inject(Keycloak); private keycloakEvents = inject(KEYCLOAK_EVENT_SIGNAL); private router = inject(Router); + private messageService = inject(MessageService); // User state private userSubject = new BehaviorSubject(null); @@ -22,6 +23,9 @@ export class AuthService { // Authentication state as a signal public isAuthenticated = signal(false); + // Login error state + public loginError = signal(null); + constructor(private http: HttpClient) { // Check initial state this.checkInitialAuthState(); @@ -36,6 +40,7 @@ export class AuthService { // On successful login if (event.type === KeycloakEventType.AuthSuccess) { this.isAuthenticated.set(true); + this.loginError.set(null); this.loadUserInfo(); } @@ -51,6 +56,16 @@ export class AuthService { console.error('Authentication error:', event); this.isAuthenticated.set(false); this.userSubject.next(null); + + // Mostrar mensaje de error + const errorMsg = 'Error de autenticación. Por favor, verifica tus credenciales o inténtalo más tarde.'; + this.loginError.set(errorMsg); + this.messageService.add({ + severity: 'error', + summary: 'Error de autenticación', + detail: errorMsg, + life: 5000 + }); } // On token expiration @@ -72,6 +87,14 @@ export class AuthService { } catch (error) { console.error('Error checking initial auth state:', error); this.isAuthenticated.set(false); + + // Mostrar mensaje de error + this.messageService.add({ + severity: 'error', + summary: 'Error', + detail: 'No se pudo verificar el estado de autenticación.', + life: 5000 + }); } } @@ -108,27 +131,73 @@ export class AuthService { } catch (error) { console.error('Error loading user profile:', error); this.userSubject.next(null); + + this.messageService.add({ + severity: 'error', + summary: 'Error', + detail: 'No se pudo cargar la información del usuario.', + life: 5000 + }); } } - login(redirectUri?: string): Promise { - return this.keycloak.login({ - redirectUri: redirectUri || window.location.origin - }); + async login(redirectUri?: string): Promise { + try { + this.loginError.set(null); + await this.keycloak.login({ + redirectUri: redirectUri || window.location.origin + }); + } catch (error) { + console.error('Login error:', error); + this.loginError.set('Error al iniciar sesión. Por favor, inténtalo de nuevo.'); + + this.messageService.add({ + severity: 'error', + summary: 'Error de inicio de sesión', + detail: 'No se pudo iniciar sesión. Por favor, inténtalo de nuevo.', + life: 5000 + }); + + throw error; + } } - logout(): Promise { - return this.keycloak.logout({ - redirectUri: window.location.origin - }); + async logout(): Promise { + try { + await this.keycloak.logout({ + redirectUri: window.location.origin + }); + } catch (error) { + console.error('Logout error:', error); + // Intento manual de navegar a login en caso de error + this.router.navigate(['/login']); + + this.messageService.add({ + severity: 'error', + summary: 'Error', + detail: 'Error al cerrar sesión.', + life: 5000 + }); + } } isLoggedIn(): Observable { - return from(Promise.resolve(this.keycloak.authenticated)); + try { + // Usar directamente la propiedad authenticated de Keycloak + return from(Promise.resolve(this.keycloak.authenticated || false)); + } catch (error) { + console.error('Error al verificar autenticación:', error); + return from(Promise.resolve(false)); + } } getToken(): Promise { - return Promise.resolve(this.keycloak.token || ''); + try { + return Promise.resolve(this.keycloak.token || ''); + } catch (error) { + console.error('Error al obtener token:', error); + return Promise.resolve(''); + } } async updateToken(minValidity = 30): Promise { @@ -136,7 +205,16 @@ export class AuthService { return await this.keycloak.updateToken(minValidity); } catch (error) { console.error('Error refreshing token:', error); - await this.login(); + // No redireccionar automáticamente al login, mostrar mensaje primero + this.messageService.add({ + severity: 'warn', + summary: 'Sesión expirada', + detail: 'Tu sesión ha expirado. Por favor, inicia sesión nuevamente.', + life: 5000 + }); + + // Esperar un momento para que el usuario vea el mensaje + setTimeout(() => this.login(), 2000); return false; } }