To allow access to the admin route I have to check two things:
AdminGuard
import {ActivatedRouteSnapshot, CanActivate, Router,
RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import {AngularFireAuth} from 'angularfire2/auth';
import {Injectable} from '@angular/core';
import {DbActionsService} from '../services/db-actions.service';
import {AuthService} from '../services/auth.service';
import 'rxjs/add/operator/map';
@Injectable()
export class AdminGuard implements CanActivate {
constructor(private afAuth: AngularFireAuth, private router: Router, private dbAction: DbActionsService, private authService : AuthService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
if (this.afAuth.auth.currentUser) {
return this.dbAction.getUserProfileData(this.afAuth.auth.currentUser.email).map((user) => {
if (user[0].admin) {
return true;
} else {
this.router.navigate(['/']);
return false;
}
}).take(1)
} else {
this.router.navigate(['/']);
}
}
}
Service Function
getUserProfileData(userEmail: string) {
this.dataRef = this.afDatabase.list('data/users', ref => ref.orderByChild('profile/email').equalTo(userEmail));
this.data = this.dataRef.snapshotChanges().map(changes => {
return changes.map(c => ({ key: c.payload.key, ...c.payload.val() }));
});
return this.data;
}
This works fine, however, my main problem is when I refresh (or initially load) the page on that has AdminGuard it always redirects me to the home page since the AdminGuard doesn't wait for the authentication response.
A new AuthService
import { Injectable } from '@angular/core';
import {AngularFireAuth} from 'angularfire2/auth';
import {Observable} from 'rxjs/Observable';
@Injectable()
export class AuthService {
private user: any;
constructor(private afAuth: AngularFireAuth) { }
setUser(user) {
this.user = user;
}
getAuthenticated(): Observable<any> {
return this.afAuth.authState;
}
}
New AdminGuard
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {Observable} from 'rxjs/Observable';
import {AngularFireAuth} from 'angularfire2/auth';
import {Injectable} from '@angular/core';
import {DbActionsService} from '../services/db-actions.service';
import {AuthService} from '../services/auth.service';
import 'rxjs/add/operator/map';
@Injectable()
export class AdminGuard implements CanActivate {
constructor(private afAuth: AngularFireAuth, private router: Router, private dbAction: DbActionsService, private authService : AuthService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
return this.authService.getAuthenticated().map(user => {
this.authService.setUser(user);
return user ? true : false;
});
}
}
This works finde with Auth Check on initial load, but how I implement also the database conditional to check if the user is an admin? I have no idea...
AuthGuard is used to protect the routes from unauthorized access in angular. How AuthGuard Works? Auth guard provide lifecycle event called canActivate. The canActivate is like a constructor.
Angular's route guards are interfaces which can tell the router whether or not it should allow navigation to a requested route. They make this decision by looking for a true or false return value from a class which implements the given guard interface.
CanActivate basically answers the question: “Does the user have access to this route?” We use this guard to prevent access to users who are not authorized to access a route.
CanActivatelinkInterface that a class can implement to be a guard deciding if a route can be activated. If all guards return true , navigation continues. If any guard returns false , navigation is cancelled.
Here's a guard that should work based on your code, let's break it down:
authState
observable.switchMap
to switch to the database array observable you want.import { tap, map, switchMap, take } from 'rxjs/operators;
// ...omitted
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
return this.afAuth.authState.pipe(
take(1),
switchMap(user => {
return this.dbAction.getUserProfileData(user.email)
}),
map(profile => !!(profile.length && profile[0].admin),
tap(isAdmin => {
if (isAdmin) {
console.log('admin user, you shall pass')
} else {
console.log('non-admin user, go home')
this.router.navigate(['/']);
}
})
)
}
I have done many guard implementations with Angular/Firebase and have some opinions about what works best - I think this role based auth screencast might help you improve on your current approach
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With