30

I need to check te back-end for authentication status, however te code completes before te observable return is finished. Which would result in an undifined.

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    this.isAuthenticated();        
    return this.authenticated; 
}

isAuthenticated(){
    this.loginService.isAuthenticated()
        .subscribe(status => this.authenticated = status)
} 

How would i change this code so i wait for the observable to complete to get the authenticated status before the code returns.

Note: the Angular canActivate method does not allow me to write the code as shown below:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    this.loginService.isAuthenticated()
        .subscribe(status => {
            this.authenticated = status;
            return this.authenticated;
        });
}

This results in the followin error:

Class 'AuthGuard' incorrectly implements interface 'CanActivate'.
Types of property 'canActivate' are incompatible. Type '(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => void' is not assignable to type '(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => boolean | Observable | Pr...'. Type 'void' is not assignable to type 'boolean | Observable | Promise'.

A suggestion for a solution for this error would also be helpful.

phicon
  • 3,549
  • 5
  • 32
  • 62
  • I had the exact same issue while trying to implement a custom auth guard, that authorizes/not logged in users, with different roles. You saved my day. – Dejazmach Jan 23 '19 at 22:12

2 Answers2

28

Solved the issue with async / await and promise

The LoginService first import toPromise:

import 'rxjs/add/operator/toPromise';

Then created an async method in the LoginService

  async isAuthenticated(){
     const response = await this.http.get('/login/authenticated').toPromise();
     return response.json();
  }

Then in the component:

async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    this.loginStatus = await this.loginService.isAuthenticated();

    console.log("LOGGED IN STATUS: " + this.loginStatus);

    if (this.loginStatus == true){
        return true;
    }

    // not logged in so redirect to login page with the return url
    this.router.navigate(['/layout/login'], { queryParams: { returnUrl: state.url } });    
}
phicon
  • 3,549
  • 5
  • 32
  • 62
8

You can return the observable as Observable<boolean>

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.loginService.isAuthenticated()
        .map(status => {
            console.log(status);
            this.authenticated = status;
            return true;
        }).catch((err) => {
            console.log(err);
            return Observable.of(false);
        });
}
eko
  • 39,722
  • 10
  • 72
  • 98
  • canActivate should return true or false, instead of an Observable, is this possible? – phicon May 13 '17 at 12:54
  • Also, if subscribed the back-end always returns; true or false. Shouldnt i add the Subscribe() method? – phicon May 13 '17 at 13:40
  • 1
    @phicon This is exactly what your error message says, `boolean | Observable | Promise`. It should return a boolean, an observable of a boolean or a promise of a boolean. `this.authenticated` is undefined in your original code. – Estus Flask May 13 '17 at 13:41