1

I have been trying to implement AuthGuard properly in my webapp. Currently when I navigate within the app, it works fine. But when I refresh, the authService.loggedIn is processed as false before the AuthService finished executing.

Here is my code:

auth.guard.ts



    import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
    import {Injectable} from '@angular/core';
    import {AuthService} from './auth.service';

    @Injectable()
    export class AuthGuard implements CanActivate {
      constructor(private authService: AuthService, private router: Router) {
      }

      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        if (state.url === '/login') {
          if (this.authService.loggedIn) {
            this.router.navigate(['/']).then().catch();
          } else {
            return true;
          }
        } else {
          if (this.authService.loggedIn) {
            return true;
          } else {
            this.router.navigate(['/login']).then().catch();
          }
        }
      }
    }

auth.service



    import {Injectable, OnInit} from '@angular/core';
    import {AngularFireAuth} from '@angular/fire/auth';
    import {auth} from 'firebase';
    import {Router} from '@angular/router';
    import {Subject} from 'rxjs';

    @Injectable({
      providedIn: 'root'
    })
    export class AuthService implements OnInit {
      loggedIn = false;

      constructor(public afAuth: AngularFireAuth, private router: Router) {
        this.afAuth.authState.subscribe((user) => {
          if (user) {
            this.loggedIn = true;
          }
        });
      }

      ngOnInit() {
      }

      ...
    }

I research online and they mentioned different approach (e.g. https://gist.github.com/codediodeio/3e28887e5d1ab50755a32c1540cfd121) but could not make it work on my app.

One error I encounter when I try this approach is "ERROR in src/app/auth.guard.ts(20,8): error TS2339: Property 'take' does not exist on type 'Observable'." I use

import {AngularFireAuth} from '@angular/fire/auth';

and not

import { AngularFireAuth } from 'angularfire2/auth';

Any help / suggestion is appreciated.

Thanks everyone.

gritbranch
  • 199
  • 3
  • 14

2 Answers2

3

I was able to make AuthGuard with browser refresh work.

This link helped a lot: https://gist.github.com/codediodeio/3e28887e5d1ab50755a32c1540cfd121

I just used pipe() to chain operators and used tap() instead of do() since I'm using newer version of rxjs.

Here is the code:



    ...
    import {Observable} from 'rxjs';

    import {take, map, tap} from 'rxjs/operators';

    @Injectable()
    export class AuthGuard implements CanActivate {
      constructor(private router: Router, private afAuth: AngularFireAuth) {
      }

      canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable {
        return this.afAuth.authState
          .pipe(
            take(1),
            map(user => !!user),
            tap(
              loggedIn => {
                if (!loggedIn) {
                  this.router.navigate(['/login']);
                }
              }
            )
          );
      }
    }

Thanks.

gritbranch
  • 199
  • 3
  • 14
  • 1
    Thank you, this worked great! I tried to get the state from the AuthService component as described in the initial problem too, but Canactivate ran before the AuthService getter received the updated auth state from the subscription in my AuthService Constructor, so it returned false on the Refresh. – Steve Klock Aug 25 '20 at 15:57
2

You can achieve this by following the steps as mentioned below:

1) First in your authService, create a behavioursubject of user EX:

user = new BehaviorSubject<any>(null); 

2) Now create a logIn function in authService

logIn(email: string, password: string) {
  // do some logging in functionality over here 
  // like authenticating the user, then emit the userInfo 
  // and save it to the storage.
  this.user.next(userData);
  localStorage.setItem('userData', userData);
  // also, now here after logging in navigate to your next url which is protected from guard or other some other url.
}

3) Now create a autoLogin Function

autoLogin() {
 const userData = localStorage.getItem('userData');
 if (!userData) {
    this.user.next(null);
    return;
 }
 this.user.next(userData);
}

4) Now in auth.guard.ts write the following code

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
import { map, take } from 'rxjs/operators';


@Injectable({providedIn: 'root'})
export class AuthGuard implements CanActivate {
    constructor(private authService: AuthService, private router: Router) { }

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
        this.authService.autoLogin();
        return this.authService.user.pipe(take(1), map(user => {
            const isAuth = !!user;
            if (isAuth) {
                return true;
            } else {
                return this.router.createUrlTree(['/auth']);
            }
        }));
    }
}

5) Don't forget to add the canActivate object in your route configuration, where you want to apply the guard

For Example

{ path : 'root', component: SuperadminComponent, canActivate: [AuthGuard] }

Note 1 : if {providedIn: 'root'} is not provided in injectable in AuthGuard then don't forget to add it in the providers array of app.module.ts or in any module where you want to use

Note 2 : On Logout clear the storage and emit the user as null.

Hope this will help you or somebody else!

Aman Kumar Gupta
  • 2,640
  • 20
  • 18