J'essaie d'utiliser des gardes de routeur Angular2 pour restreindre l'accès à certaines pages de mon application. J'utilise l'authentification Firebase. Afin de vérifier si un utilisateur est connecté à Firebase, je dois appeler .subscribe()
sur l'objet FirebaseAuth
avec un rappel. C'est le code pour la garde:
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;
});
}
}
Lorsque vous naviguez sur une page protégée, authenticated
ou not authenticated
est imprimé sur la console (après un certain délai d'attente de la réponse de firebase). Cependant, la navigation n'est jamais terminée. De plus, si je ne suis pas connecté, je suis redirigé vers la route /login
. Donc, le problème que j'ai est que return true
n'affiche pas la page demandée à l'utilisateur. Je suppose que c'est parce que j'utilise un rappel, mais je suis incapable de comprendre comment le faire autrement. Des pensées?
canActivate
doit renvoyer une Observable
complétant:
@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
}
}
Il manque une return
et j'utilise map()
au lieu de subscribe()
car subscribe()
renvoie une Subscription
et non une Observable
canActivate
peut retourner une Promise
qui résout aussi une boolean
Vous pouvez utiliser Observable
pour gérer la partie logique asynchrone. Voici le code que je teste par exemple:
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);
});
}
}
}
Pour développer la réponse la plus populaire. L'API Auth pour AngularFire2 a quelque peu changé. Ceci est la nouvelle signature pour réaliser un AngularFire2 AuthGuard:
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;
});
}
}
Note: Ceci est un test assez naïf. Vous pouvez consigner l'instance de l'utilisateur pour voir si vous souhaitez tester certains aspects plus détaillés de l'utilisateur. Mais devrait au moins aider à protéger les itinéraires contre les utilisateurs qui ne sont pas connectés.
Vous pouvez retourner true | false comme une promesse.
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);
}
}
})
})
}
}
Dans la version la plus récente de AngularFire, le code suivant fonctionne (lié à la meilleure réponse). Notez l'utilisation de la méthode "pipe".
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;
}
})
);
}
}