Michael Andorfer
Michael Andorfer

Reputation: 1800

How to correctly check if user is authenticated in Angular4?

I am currently developing an Angular 4 application.

The application uses Auth0 for authentication whose syntax is quite similar to those of other authentication services.

The code for authentication looks as follows:

// auth.services.ts

@Injectable()
export class Auth {
  public lock = new Auth0Lock(myConfig.clientID, myConfig.domain, myConfig.lock);
  public userProfile: any;
  public idToken: string;
  public signUpIncomplete: boolean;

  // Configure Auth0
  private auth0 = new Auth0.WebAuth({
    domain: myConfig.domain,
    clientID: myConfig.clientID,
    redirectUri: myConfig.redirectUri,
    responseType: myConfig.responseType
  });

  // Create a stream of logged in status to communicate throughout app
  private loggedIn: boolean;
  private loggedIn$ = new BehaviorSubject<boolean>(this.loggedIn);

  constructor(private router: Router, private http: Http) {
    // Set userProfile attribute of already saved profile
    this.userProfile = JSON.parse(localStorage.getItem('profile'));
  }

  public isAuthenticated(): boolean {
    // Check whether the id_token is expired or not
    console.log("isAuthenticated");
    return tokenNotExpired('id_token');
  }

  public login(username?: string, password?: string): Promise<any> {
    if (!username && !password) {
      return;
    }
    return this.processLogin(username, password);
  }

  public logout() {
    // Remove tokens and profile and update login status subject
    localStorage.removeItem('token');
    localStorage.removeItem('id_token');
    localStorage.removeItem('profile');
    this.idToken = '';
    this.userProfile = null;
    this.setLoggedIn(false);

    // Go back to the home rout
    this.router.navigate(['/']);
  }

  public loginWithWidget(): void {
    this.lock.show();
  }

  // Call this method in app.component
  // if using path-based routing <== WE ARE USING PATH BASED ROUTING
  public handleAuth(): void {
    // When Auth0 hash parsed, get profile
    this.auth0.parseHash({}, (err, authResult) => {
      if (authResult && authResult.accessToken && authResult.idToken) {
        // window.location.hash = '';
        this._getProfile(authResult);
        this.router.navigate(['/']);
      } else if (err) {
        this.router.navigate(['/']);
        console.error(`Error: ${err.error}`);
      }
    });
  }

  private setLoggedIn(value: boolean) {
    // Update login status subject
    this.loggedIn$.next(value);
    this.loggedIn = value;
  }

  private _getProfile(authResult) {
    // Use access token to retrieve user's profile and set session
    // const lock2 = new Auth0Lock(myConfig.clientID, myConfig.domain, myConfig.lock)
    const idToken = authResult.id_token || authResult.idToken;
    this.lock.getProfile(idToken, (error, profile) => {
      if (error) {
        // Handle error
        console.error(error.error);
        return;
      }
      // Save session data and update login status subject
      this._setSession(authResult, profile);
      if (!this.checkUserHasRole(profile)) {
        this.router.navigate(['/signup/complete']);
      }
    });
  }

  private _setSession(authResult, profile) {
    // Save session data and update login status subject
    localStorage.setItem('token', authResult.access_token || authResult.accessToken);
    localStorage.setItem('id_token', authResult.id_token || authResult.idToken);
    localStorage.setItem('profile', JSON.stringify(profile));
    this.idToken = authResult.id_token || authResult.idToken;
    this.setLoggedIn(true);
    this.userProfile = profile;
    this.checkUserHasRole(profile);
  }

  private processLogin(username?: string, password?: string): Promise<any> {
    const options = {
      client_id: myConfig.clientID,
      connection: postConfig.body.connection,
      grant_type: 'password',
      username,
      password,
      scope: myConfig.scope
    };
    const headers = new Headers();
    headers.append('content-type', 'application/json');
    const reqOpts = new RequestOptions({
      method: RequestMethod.Post,
      url: postConfig.urlLogin,
      headers,
      body: options
    });
    return this.http.post(postConfig.urlLogin, options, reqOpts)
      .toPromise()
      .then(this.extractData)
      .then((data) => { this._getProfile(data); })
      .catch(this.handleLoginError);
  }
  ...
}

The problem I have is that the isAuthenticated method is called more than 1000 times on page load. Additionally, it is called each time I move the mouse in the window object.

Although I followed the tutorial of Auth0 step by step, I suppose that this cannot be the expected behaviour as it will and already does affect the performance of the application.

What could be a reason for the fact that isAuthenticated is called that often? Do I have to implement a timer which does the check periodically after a specified time or do I have to implement an observer? Are there any obvious mistakes in my code?

Upvotes: 4

Views: 16397

Answers (2)

Michael Andorfer
Michael Andorfer

Reputation: 1800

Finally, I have found out the reason.

My navigation component implements a transition effect using a host listener @HostListener('mouseover', ['$event']). I accidentally added the host listener to the window object. For that reason, each time I moved the mouse the host listener was fired. Since my navigation template contains *ngIf="auth.isAuthenticated()" to display some navigation items just in case the user is authenticated, isAuthenticated was fired so many times.

Upvotes: 3

joh04667
joh04667

Reputation: 7427

The reason why isAuthenticated is called so many times depends on the component that is calling it, which you don't have here. isAuthenticated is never called once in this service.

Set up a router guard instead, called CanActivate by the Angular API. This will be called on route activation and redirects can happen on failures before the routed component can even be loaded, and will only be called once. Use that to call service.isAuthenticated instead.

login.guard.ts

import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { Auth } from './auth.service';

@Injectable()
export class LoginGuard implements CanActivate {
    constructor(public auth: Auth, protected router: Router) { }

    canActivate() {
        if (!this.auth.isAuthenticated()) {
            this.router.navigate(['/']);
            return false;
        }
        return true;
    }

In your routes definition

export const routes: Routes = [
    { path: '', component: SomeComponent },
    { path: 'main', component: ProtectedComponent, canActivate: [LoginGuard] }
]

It shouldn't be called 1000s of times in any case. I'm guessing there's some looping going on in your component or injection tree.

Upvotes: 5

Related Questions