keycloack funcionando

This commit is contained in:
luis cespedes 2025-05-13 15:58:00 -04:00
parent 01b93eca37
commit c84c9a95c8
6 changed files with 172 additions and 82 deletions

View File

@ -1,5 +1,5 @@
{ {
"realm": "angular-app", "realm": "aangular-app",
"auth-server-url": "http://localhost:8080/", "auth-server-url": "http://localhost:8080/",
"resource": "angular-app", "resource": "angular-app",
"credentials": { "credentials": {

View File

@ -35,34 +35,42 @@ export class AppComponent implements OnInit, OnDestroy {
// Authentication success handling // Authentication success handling
if (event.type === KeycloakEventType.AuthSuccess) { if (event.type === KeycloakEventType.AuthSuccess) {
console.log('Authentication successful, redirecting to home'); console.log('Authentication successful');
this.router.navigate(['/inicio']); // We'll let the guards and login component handle redirections
// No redirect here to avoid conflicts
} }
// Authentication error handling // Authentication error handling
if (event.type === KeycloakEventType.AuthError) { if (event.type === KeycloakEventType.AuthError) {
console.error('Authentication error, redirecting to login'); console.error('Authentication error');
this.router.navigate(['/login']); // Only redirect to login if not already there
if (this.router.url !== '/login') {
this.router.navigate(['/login']);
}
} }
// Logout handling // Logout handling
if (event.type === KeycloakEventType.AuthLogout) { if (event.type === KeycloakEventType.AuthLogout) {
console.log('Logged out, redirecting to login'); console.log('Logged out');
this.router.navigate(['/login']); // Only redirect to login if not already there
if (this.router.url !== '/login') {
this.router.navigate(['/login']);
}
} }
}); });
} }
ngOnInit() { ngOnInit() {
// Subscribe to navigation events // Subscribe to navigation events - using standard unsubscribe pattern
this.router.events this.subscriptions.push(
.pipe( this.router.events
filter(event => event instanceof NavigationEnd), .pipe(
takeUntilDestroyed() filter(event => event instanceof NavigationEnd)
) )
.subscribe(() => { .subscribe(() => {
console.log('Navigation completed to:', this.router.url); console.log('Navigation completed to:', this.router.url);
}); })
);
// Check authentication status on load // Check authentication status on load
this.checkAuthenticationStatus(); this.checkAuthenticationStatus();
@ -78,25 +86,20 @@ export class AppComponent implements OnInit, OnDestroy {
const isLoggedIn = await this.keycloak.authenticated; const isLoggedIn = await this.keycloak.authenticated;
console.log('Initial authentication status:', isLoggedIn ? 'Authenticated' : 'Not authenticated'); console.log('Initial authentication status:', isLoggedIn ? 'Authenticated' : 'Not authenticated');
if (isLoggedIn) { // Let the guards handle the protected routes
// Already authenticated, check if on protected route // Only do minimal checks here to avoid redirect loops
if (this.router.url === '/login') {
this.router.navigate(['/inicio']); // If the user is on login page but already authenticated, send to home
} if (isLoggedIn && this.router.url === '/login') {
} else { console.log('Already authenticated, redirecting from login to home');
// Not authenticated but trying to access a protected route this.router.navigate(['/inicio']);
const isPublicRoute = this.router.url === '/login'; return;
}
if (!isPublicRoute) {
console.log('User not authenticated, redirecting to Keycloak login'); // If not authenticated and on a protected route, go to Keycloak login
// Store current URL for redirect after login if (!isLoggedIn && this.router.url !== '/login') {
const currentUrl = this.router.url; // We'll let the auth guard handle this
console.log('Not authenticated on protected route');
// Start Keycloak login flow
this.keycloak.login({
redirectUri: window.location.origin + currentUrl
});
}
} }
} catch (error) { } catch (error) {
console.error('Error checking authentication status:', error); console.error('Error checking authentication status:', error);

View File

@ -51,15 +51,16 @@ export const appConfig: ApplicationConfig = {
// Provide Keycloak with initOptions - this automatically creates an APP_INITIALIZER // Provide Keycloak with initOptions - this automatically creates an APP_INITIALIZER
provideKeycloak({ provideKeycloak({
config: { config: {
url: 'http://localhost:8080', url: 'http://localhost:8080', // Asegúrate de que esta URL coincida con tu servidor Keycloak
realm: 'angular-app', realm: 'angular-app', // Asegúrate de que este realm existe en tu servidor Keycloak
clientId: 'angular-app' clientId: 'angular-app',
}, },
initOptions: { 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`, silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`,
checkLoginIframe: false, checkLoginIframe: false,
pkceMethod: 'S256' // Added for better security pkceMethod: 'S256',
enableLogging: true
}, },
// Configure the interceptor by providing the URLs where to include the token // Configure the interceptor by providing the URLs where to include the token
providers: [ providers: [

View File

@ -4,7 +4,7 @@ import Keycloak from 'keycloak-js';
import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router';
import { createAuthGuard, AuthGuardData } from 'keycloak-angular'; import { createAuthGuard, AuthGuardData } from 'keycloak-angular';
// This is a simplified direct implementation // Simple implementation for authentication guard
export const authGuard: CanActivateFn = async ( export const authGuard: CanActivateFn = async (
route: ActivatedRouteSnapshot, route: ActivatedRouteSnapshot,
state: RouterStateSnapshot state: RouterStateSnapshot
@ -12,28 +12,24 @@ export const authGuard: CanActivateFn = async (
const keycloak = inject(Keycloak); const keycloak = inject(Keycloak);
const router = inject(Router); 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 { try {
await keycloak.login({ // Check if user is authenticated
redirectUri: window.location.origin + returnUrl 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) { } catch (error) {
console.error('Error during login redirect:', error); console.error('Error checking authentication:', error);
// Fallback to local login component if Keycloak login fails // Fallback to login on error
return router.createUrlTree(['/login'], { queryParams: { returnUrl } }); return router.createUrlTree(['/login']);
} }
}; };

View File

@ -12,6 +12,8 @@ import { ToastModule } from 'primeng/toast';
import { MessageService } from 'primeng/api'; import { MessageService } from 'primeng/api';
import { FooterComponent } from "../../components/footer/footer.component"; import { FooterComponent } from "../../components/footer/footer.component";
import { AuthService } from '../../services/auth.service'; import { AuthService } from '../../services/auth.service';
import { take } from 'rxjs/operators';
import { firstValueFrom } from 'rxjs';
@Component({ @Component({
selector: 'app-login', selector: 'app-login',
@ -43,16 +45,22 @@ export class LoginComponent implements OnInit {
constructor(private messageService: MessageService) {} constructor(private messageService: MessageService) {}
async ngOnInit() { ngOnInit() {
// Obtener el returnUrl de los query params si existe // Obtener el returnUrl de los query params si existe
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/inicio'; this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/inicio';
// Verificar si ya está autenticado y redirigir // Simplificación - verificar autenticación sin promesas anidadas
this.authService.isLoggedIn().subscribe(isLoggedIn => { this.authService.isLoggedIn().subscribe({
if (isLoggedIn) { next: (isLoggedIn) => {
console.log('Usuario ya autenticado, redirigiendo...'); if (isLoggedIn) {
this.router.navigateByUrl(this.returnUrl); 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; this.loading = true;
try { try {
// Construir el redirectUri para que vuelva a la URL que estaba intentando acceder // Verificar si la URL de retorno es válida
const redirectUri = window.location.origin + this.returnUrl; const effectiveReturnUrl = this.returnUrl === '/' ? '/inicio' : this.returnUrl;
// Construir el redirectUri
const redirectUri = window.location.origin + effectiveReturnUrl;
console.log('Iniciando login con redirectUri:', redirectUri); console.log('Iniciando login con redirectUri:', redirectUri);
// Iniciar el flujo de autenticación de Keycloak
await this.authService.login(redirectUri); 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) { } catch (error) {
console.error('Error al iniciar sesión:', error); console.error('Error al iniciar sesión:', error);
this.loading = false; this.loading = false;

View File

@ -4,7 +4,7 @@ import { BehaviorSubject, Observable, from } from 'rxjs';
import { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType } from 'keycloak-angular'; import { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType } from 'keycloak-angular';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import Keycloak from 'keycloak-js'; import Keycloak from 'keycloak-js';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { MessageService } from 'primeng/api';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -14,6 +14,7 @@ export class AuthService {
private keycloak = inject(Keycloak); private keycloak = inject(Keycloak);
private keycloakEvents = inject(KEYCLOAK_EVENT_SIGNAL); private keycloakEvents = inject(KEYCLOAK_EVENT_SIGNAL);
private router = inject(Router); private router = inject(Router);
private messageService = inject(MessageService);
// User state // User state
private userSubject = new BehaviorSubject<any>(null); private userSubject = new BehaviorSubject<any>(null);
@ -22,6 +23,9 @@ export class AuthService {
// Authentication state as a signal // Authentication state as a signal
public isAuthenticated = signal<boolean>(false); public isAuthenticated = signal<boolean>(false);
// Login error state
public loginError = signal<string | null>(null);
constructor(private http: HttpClient) { constructor(private http: HttpClient) {
// Check initial state // Check initial state
this.checkInitialAuthState(); this.checkInitialAuthState();
@ -36,6 +40,7 @@ export class AuthService {
// On successful login // On successful login
if (event.type === KeycloakEventType.AuthSuccess) { if (event.type === KeycloakEventType.AuthSuccess) {
this.isAuthenticated.set(true); this.isAuthenticated.set(true);
this.loginError.set(null);
this.loadUserInfo(); this.loadUserInfo();
} }
@ -51,6 +56,16 @@ export class AuthService {
console.error('Authentication error:', event); console.error('Authentication error:', event);
this.isAuthenticated.set(false); this.isAuthenticated.set(false);
this.userSubject.next(null); 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 // On token expiration
@ -72,6 +87,14 @@ export class AuthService {
} catch (error) { } catch (error) {
console.error('Error checking initial auth state:', error); console.error('Error checking initial auth state:', error);
this.isAuthenticated.set(false); 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) { } catch (error) {
console.error('Error loading user profile:', error); console.error('Error loading user profile:', error);
this.userSubject.next(null); 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<void> { async login(redirectUri?: string): Promise<void> {
return this.keycloak.login({ try {
redirectUri: redirectUri || window.location.origin 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<void> { async logout(): Promise<void> {
return this.keycloak.logout({ try {
redirectUri: window.location.origin 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<boolean> { isLoggedIn(): Observable<boolean> {
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<string> { getToken(): Promise<string> {
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<boolean> { async updateToken(minValidity = 30): Promise<boolean> {
@ -136,7 +205,16 @@ export class AuthService {
return await this.keycloak.updateToken(minValidity); return await this.keycloak.updateToken(minValidity);
} catch (error) { } catch (error) {
console.error('Error refreshing token:', 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; return false;
} }
} }