Estoy tratando de usar los protectores del enrutador Angular2 para restringir el acceso a algunas páginas en mi aplicación. Estoy usando Firebase Authentication. Para verificar si un usuario ha iniciado sesión con Firebase, tengo que llamar .subscribe()
al FirebaseAuth
objeto con una devolución de llamada. Este es el código para el guardia:
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AngularFireAuth } from "angularfire2/angularfire2";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private auth: AngularFireAuth, private router: Router) {}
canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<boolean>|boolean {
this.auth.subscribe((auth) => {
if (auth) {
console.log('authenticated');
return true;
}
console.log('not authenticated');
this.router.navigateByUrl('/login');
return false;
});
}
}
Cuando se navega a una página que tiene el guardia en ella, authenticated
o not authenticated
se imprime en la consola (después de una demora esperando la respuesta de firebase). Sin embargo, la navegación nunca se completa. Además, si no he iniciado sesión, se me redirige a la /login
ruta. Entonces, el problema que tengo es return true
que no muestra la página solicitada al usuario. Supongo que esto se debe a que estoy usando una devolución de llamada, pero no puedo averiguar cómo hacerlo de otra manera. ¿Alguna idea?
fuente
Respuestas:
canActivate
necesita devolver unObservable
que complete:@Injectable() export class AuthGuard implements CanActivate { constructor(private auth: AngularFireAuth, private router: Router) {} canActivate(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<boolean>|boolean { return this.auth.map((auth) => { if (auth) { console.log('authenticated'); return true; } console.log('not authenticated'); this.router.navigateByUrl('/login'); return false; }).first(); // this might not be necessary - ensure `first` is imported if you use it } }
Falta un
return
y utilizo enmap()
lugar desubscribe()
porquesubscribe()
devuelve unSubscription
no unObservable
fuente
auth
es un valor emitido por el observable (podría ser solotrue
ofalse
). El observable se ejecuta cuando el enrutador se suscribe. Tal vez falta algo en su configuración.Puede utilizar
Observable
para manejar la parte lógica asíncrona. Aquí está el código que pruebo, por ejemplo:import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { DetailService } from './detail.service'; @Injectable() export class DetailGuard implements CanActivate { constructor( private detailService: DetailService ) {} public canActivate(): boolean|Observable<boolean> { if (this.detailService.tempData) { return true; } else { console.log('loading...'); return new Observable<boolean>((observer) => { setTimeout(() => { console.log('done!'); this.detailService.tempData = [1, 2, 3]; observer.next(true); observer.complete(); }, 1000 * 5); }); } } }
fuente
canActivate
puede devolver unPromise
que resuelve unboolean
demasiadofuente
Puede devolver verdadero | falso como promesa.
import {Injectable} from '@angular/core'; import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router'; import {Observable} from 'rxjs'; import {AuthService} from "../services/authorization.service"; @Injectable() export class AuthGuard implements CanActivate { constructor(private router: Router, private authService:AuthService) { } canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { return new Promise((resolve, reject) => { this.authService.getAccessRights().then((response) => { let result = <any>response; let url = state.url.substr(1,state.url.length); if(url == 'getDepartment'){ if(result.getDepartment){ resolve(true); } else { this.router.navigate(['login']); resolve(false); } } }) }) } }
fuente
return this.authService.getAccessRights().then...
y devolver el resultado booleano sin terminarresolve
.Para ampliar la respuesta más popular. La API de autenticación para AngularFire2 ha cambiado algo. Esta es una nueva firma para lograr un AuthGuard de AngularFire2:
import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { AngularFireAuth } from 'angularfire2/auth'; import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; @Injectable() export class AuthGuardService implements CanActivate { constructor( private auth: AngularFireAuth, private router : Router ) {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot):Observable<boolean>|boolean { return this.auth.authState.map(User => { return (User) ? true : false; }); } }
Nota: esta es una prueba bastante ingenua. Puede iniciar sesión en la consola de la instancia de Usuario para ver si le gustaría probar con algún aspecto más detallado del usuario. Pero al menos debería ayudar a proteger las rutas contra los usuarios que no están conectados.
fuente
En la versión más reciente de AngularFire, el siguiente código funciona (relacionado con la mejor respuesta). Tenga en cuenta el uso del método "tubería".
import { Injectable } from '@angular/core'; import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router'; import {AngularFireAuth} from '@angular/fire/auth'; import {map} from 'rxjs/operators'; import {Observable} from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthGuardService implements CanActivate { constructor(private afAuth: AngularFireAuth, private router: Router) { } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { return this.afAuth.authState.pipe( map(user => { if(user) { return true; } else { this.router.navigate(['/login']); return false; } }) ); } }
fuente
En mi caso, necesitaba manejar un comportamiento diferente dependiendo del error de estado de respuesta. Así es como me funciona con RxJS 6+:
@Injectable() export class AuthGuard implements CanActivate { constructor(private auth: AngularFireAuth, private router: Router) {} public canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> | boolean { return this.auth.pipe( tap({ next: val => { if (val) { console.log(val, 'authenticated'); return of(true); // or if you want Observable replace true with of(true) } console.log(val, 'acces denied!'); return of(false); // or if you want Observable replace true with of(true) }, error: error => { let redirectRoute: string; if (error.status === 401) { redirectRoute = '/error/401'; this.router.navigateByUrl(redirectRoute); } else if (error.status === 403) { redirectRoute = '/error/403'; this.router.navigateByUrl(redirectRoute); } }, complete: () => console.log('completed!') }) ); } }
En algunos casos, es posible que esto no funcione, al menos en la
next
parte deltap
operador . Quítelo y agregue el bien antiguomap
como a continuación:public canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable<boolean> | boolean { return this.auth.pipe( map((auth) => { if (auth) { console.log('authenticated'); return true; } console.log('not authenticated'); this.router.navigateByUrl('/login'); return false; }), tap({ error: error => { let redirectRoute: string; if (error.status === 401) { redirectRoute = '/error/401'; this.router.navigateByUrl(redirectRoute); } else if (error.status === 403) { redirectRoute = '/error/403'; this.router.navigateByUrl(redirectRoute); } }, complete: () => console.log('completed!') }) ); }
fuente
Para mostrar otra forma de implementación. Según la documentación , y mencionado por otras respuestas, el tipo de retorno de CanActivate también puede ser una Promesa que se resuelve en booleano.
Nota : El ejemplo que se muestra está implementado en Angular 11, pero es aplicable a las versiones de Angular 2+.
Ejemplo:
import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { AuthService } from './auth.service'; @Injectable() export class AuthGuardService implements CanActivate, CanActivateChild { constructor(private authService: AuthService, private router: Router) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable < boolean | UrlTree > | Promise < boolean | UrlTree > | boolean | UrlTree { return this.checkAuthentication(); } async checkAuthentication(): Promise < boolean > { // Implement your authentication in authService const isAuthenticate: boolean = await this.authService.isAuthenticated(); return isAuthenticate; } canActivateChild( childRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable < boolean | UrlTree > | Promise < boolean | UrlTree > | boolean | UrlTree { return this.canActivate(childRoute, state); } }
fuente
async getCurrentSemester() { let boolReturn: boolean = false let semester = await this.semesterService.getCurrentSemester().toPromise(); try { if (semester['statusCode'] == 200) { boolReturn = true } else { this.router.navigate(["/error-page"]); boolReturn = false } } catch (error) { boolReturn = false this.router.navigate(["/error-page"]); } return boolReturn }
async canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { let security: any = null if (next.data) { security = next.data.security } let bool1 = false; let bool2 = false; let bool3 = true; if (this.webService.getCookie('token') != null && this.webService.getCookie('token') != '') { bool1 = true } else { this.webService.setSession("currentUrl", state.url.split('?')[0]); this.webService.setSession("applicationId", state.root.queryParams['applicationId']); this.webService.setSession("token", state.root.queryParams['token']); this.router.navigate(["/initializing"]); bool1 = false } bool2 = this.getRolesSecurity(next) if (security && security.semester) { // ---- watch this peace of code bool3 = await this.getCurrentSemester() } console.log('bool3: ', bool3); return bool1 && bool2 && bool3 }
{ path: 'userEvent', component: NpmeUserEvent, canActivate: [AuthGuard], data: { security: { semester: true } } },
fuente