Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

angular2 check if user is authenticated

Tags:

angular

I've been stuck on this for a couple days now and need some help/guidance of how to check if a user is authenticated in my angular-rc1 app.

I'm getting a token back from my server and would like to store it in local storage (unless someone has a better idea of persisting the token in the app). The main problem I'm having is that when the token changes, I'm not able to detect that change in my components.

Through reading about 20 SO posts/articles, I think I've found the best way is to create an observable in my user.service.ts and subscribe to that observable in my components that I need to detect when the user changes their token.

Method #1 BehaviorSubject - Delegation: EventEmitter or Observable in Angular2

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

export class UserService {
  // Observable userToken source
  _userToken = new BehaviorSubject<string>("");

  // Observable userToken stream
  userToken$ = this._userToken.asObservable();

  // set the user token in a way that is observable for subscribed components
  setUserToken(token) {
    this._userToken.next(token);
  }

  ...
}

import {Subscription} from 'rxjs/Subscription';

export class NavBarComponent implements OnInit{
  subscription: Subscription;
  ...

  ngOnInit(){
    this.subscription = this._userService.userToken$.subscribe(
      userToken => {
        console.log('setting user token on navbar to ' + userToken);
        this.userToken = userToken
      });
  }
}

export class LoginComponent implements OnInit {
    ...

    ngOnInit() {
    /*
      Get the authentication code from twitch and then send that authentication
      code to the server. The server does stuff and then either passes back a
      200 with key token in data, or passes back an error 4xxx code.
    */
    var routeParams:any = this._routeParams.params

    if(routeParams.code){
      this.isLoading = true;
      this._userService.authenticateTwitch(routeParams.code)
        .subscribe(resp => {
          this._userService.setUserToken(resp.key);
          this.isLoading = false;
          this._router.navigate(['Search']);
        });
    }
  }
}

Ok, so the problem with this method is that when the LoginComponent fires this._userService.setUserToken(resp.key), the subscribe event is not seen in NavbarComponent. When NavbarComponent is initialized, its console.log in the subscribe method fires, but it does not do anything when the token changes in LoginComponent.

I feel pretty good about Method #1 and I think that's the right way to go. It's just the subscribe is not picking up the change for some reason.

Method #2 Subscribe to LocalStorage Change - How can I watch for changes to localStorage in Angular2? (and the attached plunker in the Question)

Here I'm using local storage directly using "angular2-localstorage": "^0.4.0". I make a new service to handle the local storage change and then subscribe to that Observable in my components

export class StorageService {
  public userToken$: Observable<string>;
  private _userTokenObserver;
  private _userToken;

  constructor() {
    this._userToken = "";
    this.userToken$ = new Observable(observer => {
      this._userTokenObserver = observer;
    }).share();
  }

  add(value: string) {
    console.log('setting userToken to ' + value);
    this._userToken = value;
    this._userTokenObserver.next(this._userToken);
  }

  load() {
    this._userTokenObserver.next(this._userToken);
  }
}

    export class NavbarComponent {
        ...

        ngOnInit() {
          console.log('navbar init');
          this._storageService.userToken$.subscribe(latestUserToken => {
            console.log('subscribe fired on navbar with latest user token: ', latestUserToken);
            this.userToken = latestUserToken;
            console.log('user token on nav component is now: ', this.userToken);
          });

          this._storageService.load();
        }
    }

I also have similar ngOnInit code in my LoginComponent. This method is interesting because the subscribe kinda works in the LoginComponent, but not in the NavbarComponent.

I say "kinda" because I don't think the subscribe actually worked. It appears the NavbarComponent is initialized first and the token is not ready, whereas the LoginComponent is initialized after the token is available. If the token changes (such as logging out and clearing the token), the subscribe method does not pick up the change.

...at least on the LoginComponent! Logout is on the NavbarComponent, and the subscribe event fires on the NavbarComponent. So it seems the event is being contained just to the component where it happens rather than being shared to all the components that subscribe to that event.

navbar init
navbar.component.ts:34 subscribe fired on navbar with latest user token:  
navbar.component.ts:36 user token on nav component is now: 
login init
user.service.ts:31 authenticating against server with twitch token: aaaaa
storage.service.ts:19 setting userToken to bbbbb
login.component.ts:42 subscribe fired in login componenet with latest usertoken:  bbbbb login.component.ts:44 use

r token on login component is now: bbbbbb

Other Thoughts

I thought one possible reason for these problems is that all my components are rendered through RouterOutlet, and my NavbarComponent is rendered straight in the app.template.html. However, I tried moving the navbar into my components, but it still had the same behavior.

<!-- app.template.html -->
<hero-navbar></hero-navbar>
<router-outlet></router-outlet>

Another thing might be that the subscribe shouldn't go in the ngOnInit function. It seems the code runs once, but then doesn't run again. I think it's more likely the problem is in the underlying observer/subscribe relationship though...

If you have read this whole post, I congratulate you and deeply thank you for taking the time to read this, it's by far my longest SO post. Please let me know if I can provide any more info to help give ideas on how to solve this problem.

like image 379
awwester Avatar asked Jun 17 '16 14:06

awwester


People also ask

How do you check if a user is logged in angular?

canActivate: [AuthGuard, AdminRoleGuard] An AuthGuard that will check if the user is logged in and redirect to a login screen if that's not the case. An AdminRoleGuard will check if the current user is an administrator and redirect to an error screen if that's not the case.

How to authenticate and authorize user in Angular?

Open command prompt and go to project root folder. Start the application. Create a new service, AuthService to authenticate the user. Open AuthService and include below code.


1 Answers

I faced the same issue on my angular2 app. I solved it by using a common ApiService that adds the required information to the request (ie. Authorization header). Other services that make calls to my API, extend from this service.

When a user logs in, I save the token in the local storage AND set the attribute 'isAuthenticated' to true on my AppState. I also pass that attribute to my header in my app.component.html. This way your header will update automatically whenever a user logs in/out.

app.component.ts:

constructor(public appState: AppState) {
  appState.state.isAuthenticated = false;
}

app.component.html:

<navbar [isAuthenticated]="appState.state.isAuthenticated"></navbar>

navbar.component.ts:

@Input() isAuthenticated:boolean;

auth.service.ts:

 return this.http.post(this.baseUrl, body, { headers }).subscribe((result) => {
  localStorage.setItem('oauth', JSON.stringify(result.json()));
  this.appState.set('isAuthenticated', true);

  return result;
});

I hope this helps!

like image 174
Cornel Janssen Avatar answered Sep 26 '22 09:09

Cornel Janssen