0

I am using BehaviorSubject to store user info on login. For some weird reason, the user info returned by BehaviorSubject is null in AuthGuard that gets activated for my endpoint. Please help me understand what am I missing.

I have already tried to return the subject as observable, subcribe to the subject, ReplaySubject. Nothing seems to work and the user info is always null in auth guard. I have moved the AuthService provider declaration between app module and the service component. That also didn't help. Until the call goes to auth guard, the flow is normal where the user info is set. As soon as the auth guard is called, the user info is lost.

user reg: This is step where user logs in

 onSubmit() {
    this.document.location.href = environment.callbackUrl;
  }

login component: This component sets the user info

this.authService.setUserInfo(user);
if (user.id_token!=null && user.role!=null && user.isLoggedIn){
  this.router.navigate(['/view-main/datalogger-list']);
}

auth service

userSubject: BehaviorSubject<UserInfo> = new BehaviorSubject<UserInfo>(null);

setUserInfo(user: UserInfo){
  if(user && user !=null){
    this.userSubject.next(user);
  }
}

getUserInfo() {
    return this.userSubject.asObservable();
  }

auth guard: here the user info is null

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    debugger
    let userInfo:UserInfo;

    this.authService.getUserInfo().subscribe(user => {
      console.log('Evaluating in auth guard');
      console.log('User: ', user);
      //if user is logged in and role info is available from database.
      if(user && user!=null 
          && user.role!=null && user.role.trim().length>0 
          && user.wwid!=null && user.wwid.trim().length>0){
        // allow user
        userInfo = user;
        return true;
      }
      // TO-DO user logged in but do not have information from database -How i will knwo this?
      else if(user && user!=null 
              && user.wwid!=null && user.wwid.trim().length>0
              && (user.role==null || user.role=='undefined') ){
        this.router.navigate(['welcome']);
        return false;
      }else{
        // redirect user to oidc urls
        this.router.navigate(['welcome']);
        return false;
      }
    })
    return false;
  }

Expected: Return current user info Actual: Returns null

scu
  • 11
  • 1
  • 4
  • `this.document.location.href = environment.callbackUrl;` I think this line is reloading the application from the ground up. Are you sure you are not experiencing a full reload? when navigating from one page to the other in Angular, you should use the Angular router. – Alberto Chiesa Oct 29 '19 at 21:07
  • Where are you calling `setUserInfo(user)`? – Daniel Habenicht Oct 29 '19 at 21:07
  • 1
    Please share more of your code or describe the full context. It is obvious that you retrieve null since the canActivate guard is called even before user details get added. You need to triage what calls it (Page load?) – Sergey Rudenko Oct 29 '19 at 21:31
  • Sorry, the canActivate method, as your definition says, should return boolean, not *UserInfo* – MigueMike Oct 30 '19 at 07:20
  • @A.Chiesa, the callbackUrl is to all the IDP and the login component sets the user info after that call. So I think there is no reload happening there – scu Oct 30 '19 at 13:45

2 Answers2

1

I would say that the root of your problem in your canActivate method, is that at the moment is called, you still don´t have the information of the user, and as you have initialized the BehaviorSubject with null value, this is exactly what you get there.

Instead of returning the value, I would return the the userSubject as an Observable, and subscribe to it on your canActivate method:

userSubject: BehaviorSubject<UserInfo> = new BehaviorSubject<UserInfo>(null);
 setUserInfo(user: UserInfo){
    if(user && user !=null){
      this.userSubject.next(user);
    }
  }
 getUser() {
    return this.userSubject.asObservable();
  }

And the guard:

 canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {

    let userInfo:UserInfo;  
    this.authService.getUser().subscribe(user => userInfo = user);
    return true;
}

Hope that helps you!

;)

MigueMike
  • 189
  • 6
  • You've missed the return in your `canActivate()` method and the equation of the userInfo is not an equation but an assignement – Daniel Habenicht Oct 29 '19 at 21:14
  • 1
    Can't you just subscribe to BehaviorSubject? why would we need here "asObservable()"? – Sergey Rudenko Oct 29 '19 at 21:33
  • @DanielHabenicht, nope I didn´t missed. I just modified the example provided, which is not returning anything. I think it is not completed, and he also wants to do so something the **user** before returning... – MigueMike Oct 30 '19 at 06:55
  • 1
    @SergeyRudenko It is a common practice to don´t expose the Subject, and keep it as private in the service. Exposing the Observable, you will not need anymore to `complete` the BehaviorSubject manually, and use it as other Observable. – MigueMike Oct 30 '19 at 07:00
  • Yeah I get that, but it's not valid Code. Somebody looking at your example would run into the next error because canActicate never returns anything. Can you add the "do something with it" as comment to your code and make it valid? – Daniel Habenicht Oct 30 '19 at 07:02
  • @DanielHabenicht you are totally right, gotcha. I have edited the response to return *true*. Thanks! – MigueMike Oct 30 '19 at 07:07
  • @AJT82 yep, this won't work. MiguelSsRrR should return a mapped observable as the example does not respect the asynchronous call. – Daniel Habenicht Oct 30 '19 at 12:46
  • I updated the code as suggested by @MiguelSsSRrR, but still the user info returned is null. – scu Oct 30 '19 at 13:43
  • @DanielHabenicht, could you please explain more one returning a mapped observable? May be I am missing that. Thanks – scu Oct 30 '19 at 13:44
  • Like `canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { return this.authService.getUser().map(user => { return user.loggedIn }); }` – Daniel Habenicht Oct 30 '19 at 14:13
  • @scu this should be helpful: https://stackoverflow.com/questions/42366316/using-behaviorsubject-in-auth-guards-canactivate – Daniel Habenicht Oct 30 '19 at 14:16
  • Yes. I changed it to ```this.authService.getUserInfo().pipe(map(user => { userInfo = user;return userInfo.isLoggedIn;}));```. Still it wouldn't work. While debugging I see that after routing when the guard gets activated the user info subject gets initialized to null which is the initial value. – scu Oct 30 '19 at 14:40
1

Thanks everyone. All the comments were helpful and my issue was the auth service was getting initialized in my nav component. And that's the reason it always had null value. This answer helped me think in that direction: Cannot pass data with service in angular2

scu
  • 11
  • 1
  • 4