Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular AuthGuard - Checking Authentication and Database Entry Sequentially

To allow access to the admin route I have to check two things:

  1. If an user is authenticated
  2. If this user is an admin. I get the admin state from the firebase database.

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.

What I tried

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...

like image 939
asored Avatar asked Jan 31 '18 17:01

asored


People also ask

How does AuthGuard work in Angular?

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.

Which routing guard is used to check whether routing can take place or not?

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.

Which route Guard is helpful in preventing an authenticated access to a component?

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.

What is canActivate route in Angular?

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.


1 Answers

Here's a guard that should work based on your code, let's break it down:

  1. Get the user authState observable.
  2. Pipe in switchMap to switch to the database array observable you want.
  3. Map the admin property to boolean with a double bang.
  4. Use tap to handle the redirect.
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

like image 90
JeffD23 Avatar answered Oct 05 '22 13:10

JeffD23