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 FirebaseAuthobjeto 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, authenticatedo not authenticatedse 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 /loginruta. Entonces, el problema que tengo es return trueque 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:
canActivatenecesita devolver unObservableque 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
returny utilizo enmap()lugar desubscribe()porquesubscribe()devuelve unSubscriptionno unObservablefuente
authes un valor emitido por el observable (podría ser solotrueofalse). El observable se ejecuta cuando el enrutador se suscribe. Tal vez falta algo en su configuración.Puede utilizar
Observablepara 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
canActivatepuede devolver unPromiseque resuelve unbooleandemasiadofuente
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
nextparte deltapoperador . Quítelo y agregue el bien antiguomapcomo 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