4
votes

So I have to do two things in my canActivate guard of the router. First I have to check if the user is authorized, I do this by calling OpenIdcSecutiry service, and then if the user is authorized I must check the users roles by calling another service that returns UserProfile including its roles. I know I can return an Observable and in fact if I just do:

return this.oidcSecurityService.getIsAuthorized();

It works, but obviously I does not check user roles. Right now I have this:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

return this.oidcSecurityService.getIsAuthorized()
  .map(
    data => {
      if (data === false) {
        this.router.navigate(['/login']);
        return data;
      }
      if (data === true) {
        const roles = route.data['roles'] as Array<string>;
        if (roles) {
        this.profileService.getObservableUserProfileFromServer().map(userProfile => {
          const userRoles = userProfile.Roles;
          for (const role of roles) {
            if (userRoles.indexOf(role) > -1) {
              return true;
            }
          }
          return false;
        });
        }
        return data;
      }
    },
    error => {
      this.router.navigate(['/login']);
      return error;
    }
  );

this.profileService.getObservableUserProfileFromServer() is never called and I dont know how to check the user roles after I know the user is Authorized. How can I do this? Can I return the observable inside my observable?

EDIT1: I changed the code to this:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {

return this.oidcSecurityService.getIsAuthorized()
  .map(
    data => {
      if (data === false) {
        this.router.navigate(['/login']);
        return data;
      }
      if (data === true) {
        const roles = route.data['roles'] as Array<string>;
        if (roles) {
          return this.profileService.getObservableUserProfileFromServer().toPromise().then(userProfile => {
              const userRoles = userProfile.Roles;
              for (const role of roles) {
                if (userRoles.indexOf(role) > -1) {
                  return true;
                }
              }
              return false;
          }
            );
        }
        return data;
      }
    },
    error => {
      this.router.navigate(['/login']);
      return error;
    }
  );

// return this.oidcSecurityService.getIsAuthorized();

}

Now I can see how this.profileService.getObservableUserProfileFromServer is called and receive results from server, but the result is not evaluated in canActivate guard, it simply ignores the result and It says the Navigation was canceled.

EDIT FINAL:

@llai gave me the answer, two switchmaps must be used to chain several observables, final code here:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {

return this.oidcSecurityService.getIsAuthorized()
  .switchMap(
    data => {
      if (data === false) {
        this.router.navigate(['/login']);
        return Observable.of(false); // user not logged in, canActivate = false
      } else if (data === true) {
        const roles = route.data['roles'] as Array<string>;
        if (roles) {
          // return inner observable
          return this.profileService.getObservableUserProfileFromServer()
            .switchMap(userProfile => {
              const userRoles = userProfile.Roles;
              for (const role of roles) {
                if (userRoles.indexOf(role) > -1) {
                  // role found, canActivate = true
                  return Observable.of(true);
                }
              }
              // no matching role, canActivate = false
              return Observable.of(false);
            });
        } else {
          // no roles defined in route data, canActivate = false
          return Observable.of(true);
        }
      }
    });

}

1
Did you try to use switchMap instead of map and return it? 'return this.profileService.getObservableUserProfileFromServer().switchMap'Christian Benseler
Just for testing I changed the code to: if (roles) { return this.profileService.getObservableUserProfileFromServer().switchMap(userProfile => { return Observable.of(true); }); But Its not even calling the server of userProfile Servicepablito

1 Answers

6
votes

As Christian mentioned in a comment, you should be using switchMap to chain your observables together.

In your first implementation you are not returning your inner observable therefore the router never subscribes to it, so it never gets called.

In your second implementation you are "subscribing" (convert to promise + then) to the observable within the map function, which makes the inner observable fire. However since the observable is not returned to the guard, the guard is not waiting for the inner observable to complete.


Instead you should chain the observables and return that chained sequence to the guard. This will allow the guard to subscribe to the sequence and wait for the proper response.

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {

    return this.oidcSecurityService.getIsAuthorized()
      .switchMap(
        data => {
          if (data === false) {
            this.router.navigate(['/login']);
            return Observable.of(false); // user not logged in, canActivate = false
          }else if (data === true) {
            const roles = route.data['roles'] as Array<string>;
            if (roles) {
              // return inner observable
              return this.profileService.getObservableUserProfileFromServer()
                .map(userProfile => {
                  const userRoles = userProfile.Roles;
                  for (const role of roles) {
                    if (userRoles.indexOf(role) > -1) {
                      // role found, canActivate = true
                      return true;
                    }
                  }
                  // no matching role, canActivate = false
                  return false;
                });
            }else{
              // no roles defined in route data, canActivate = false
              return Observable.of(false)
            }
          }
        }
      }
    );

}