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/",
"resource": "angular-app",
"credentials": {

View File

@ -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);

View File

@ -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: [

View File

@ -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']);
}
};

View File

@ -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;

View File

@ -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<any>(null);
@ -22,6 +23,9 @@ export class AuthService {
// Authentication state as a signal
public isAuthenticated = signal<boolean>(false);
// Login error state
public loginError = signal<string | null>(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<void> {
return this.keycloak.login({
redirectUri: redirectUri || window.location.origin
});
async login(redirectUri?: string): Promise<void> {
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<void> {
return this.keycloak.logout({
redirectUri: window.location.origin
});
async logout(): Promise<void> {
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<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> {
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> {
@ -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;
}
}