Home > Software design >  Angular CanActivate guard wait for api response and not calling api everytime
Angular CanActivate guard wait for api response and not calling api everytime

Time:07-13

I have an observable to check if the user is loggedIn or not. I set that observable from auth api which is called from appComponent when application starts.

export class AppComponent implements OnInit {
    
      loggedIn$: Observable<boolean | null>;
    
      constructor(private userService:UserService) {
        this.userService.authenticate();
      }
    
      ngOnInit(): void {
        //to show header when loggedin
        this.loggedIn$ = this.userService.isAuthenticated;
      }
    }

and I have AuthGaurd which restricts unauthenticated users from navigating to inner pages.

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
        return this.userService.isAuthenticated.pipe(
            take(1),
            map((loggedin:boolean) => {
                if(loggedin) {
                    //checking for required permissions as well
                    let permission = route.data['permission'];
                    if( this.userService.hasPermission(permission)) {
                        return true; 
                    }
                }
                this.router.navigate(['unauthorised-user', { page: state.url}]);
                return false;

            })
        );
    }

and here's aut service

    private loggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
    public authenticate() {
        this.http.get<User>('<auth-api-url')
          .subscribe(
            (response: user) => {
              this.user = response;
              if (this.user) {
                document.cookie = `coockie`;
                this.loggedIn.next(true);
                this.getReferenceData();
              }else {
                this.loggedIn.next(false);
              }
            }
          );
      }
     
     get isAuthenticated(): Observable<boolean> {
         return this.loggedIn.asObservable();
     }
        
    public hasPermission(permission:string): boolean {
        return this.user?.permissions?.includes(permission);
      }

when I launch the app "/" appComponent makes call to auth api but auth guard checks and redirects user to unauthorised-user page as auth api hasn't finished yet.

I have looked at few solutions which adds auth api call into canActivate guard but then it calls api every time I navigate to different page. I would like to call the api once and then set the flag (observable) in auth service which other components can use to check if user is authenticated or not.

Thanks

CodePudding user response:

The current implementation will always deceive you as app.component.ts is not the right entry point to place your authentication code. app.module.ts is the root point to place the authentication logic.

providers: [
{
  provide: APP_INITIALIZER,
  useFactory: (us: UserService) =>
    function () {
      return new Promise((resolve, reject) => {
        us.contextPopulate().subscribe(
          (user: any) => {
            resolve(true);
          },
          (error) => resolve(error)
        );
      });
    },
  deps: [UserService],
  multi: true,
},

],

  private currentUserSubject = new BehaviorSubject<any>(null);
  public currentUser = this.currentUserSubject.asObservable().pipe(distinctUntilChanged());

  private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
  public isAuthenticated = this.isAuthenticatedSubject.asObservable();

  contextPopulate() {
    // If JWT detected, attempt to get & store user's info
    if (this.jwtService.getToken()) {
      return this.apiService.get('auth/verify_auth_token').pipe(
        map((res) => {
          this.setCurrentUser(res);
          return res;
        }),
        catchError((e) => {
          this.purgeAuth();
          return of(null);
        })
      );
    } else {
      return of(null);
    }
  }
  setCurrentUser(user) {
    // Set current user data into observable
    this.currentUserSubject.next(user);
    // Set isAuthenticated to true
    this.isAuthenticatedSubject.next(true);
  }

Here, contextPopulate is the method that calls the auth endpoint each time your app refreshes/restarts/starts. It will not proceed until receive a response from the promise(either authorized or unauthorized).

Remove the code from app.component.ts. app.module.ts and auth method from userService is all you need to implement authentication.

CodePudding user response:

  1. Need some global service added in app.module.ts for store user info after login
  2. in service add:

loginned$ = new BehaviorSubject<boolean>(false);

  1. after calligc func login() on success add this.loginned$.next(true);

  2. Add Guard like below:

    @Injectable({providedIn: 'root'})
    export class NotAuthGuard implements CanActivate {
      constructor(private service: SomeService) {}
    
      async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> {
        return of(this.service.loginned$.getValue()).toPromise();
      }
    }
    

CodePudding user response:

You are having same problem described in this link: Angular's AuthGuard allways return false on page refresh, but I am authenticated

Maybe you get some help from that post.

  • Related