Alex A.
Alex A.

Reputation: 2613

Check if the user logged in on any page change in Angular 2

Slowly but surely progressing with Angular2. And now I faced the following challenge. I want to check if the user logged in or not on every page change (in other words on load of each and every component). Of course, I can implement OnInit interface in each and every one of them, but that is code smell.

Is there any efficient way of executing anything that needed on every page of the app? I would love to hear any other suggestions about best practices of how to handle this task.

I am using this library (https://auth0.com/blog/2015/11/10/introducing-angular2-jwt-a-library-for-angular2-authentication/) for jwt based login and I already have a nice service class that encapsulates all authentication related functionality. So, the actual checking where or not the user is logged in is already done and tested.

Thanks,

Upvotes: 10

Views: 31990

Answers (4)

Thavaprakash Swaminathan
Thavaprakash Swaminathan

Reputation: 6994

This is what I did, used canActive property in app.routing.ts

 {
  path: 'dashboard',
  loadChildren: './dashboard',
  canActivate:[AuthGuard]
}, 

Please follow the below 5 minutes video tutorial

https://www.youtube.com/watch?v=0Qsg8fyKwO4

Note: This solution works fine for Angular 4

Upvotes: 1

micronyks
micronyks

Reputation: 55443

I'm showing you simple implementation with Angular2. You can take advantage of @CanActivate hook as shown blow to check whether the user is loggedIn or not with isLoggedIn function which returns promise.

NOTE: below implementation is to check whether user is loggedIn before accessing any component or not. I hope by some modification you can achieve what you want to have.

Auth.ts

import {Observable} from 'rxjs/Observable';

export class Auth {
  constructor() {
    this.loggedIn = false;
  }

  login() {
    this.loggedIn = true;
  }

  logout() {
    this.loggedIn = false;
  }

  check() {
    return Observable.of(this.loggedIn);
  }
}

isLoggedIn.ts

import {Injector} from 'angular2/core';
import {appInjector} from './appInjector';
import {Auth} from './Auth';
import {Router, ComponentInstruction} from 'angular2/router';

export const isLoggedIn = (next: ComponentInstruction, previous: ComponentInstruction) => {
    let injector: Injector = appInjector(); // get the stored reference to the injector
    let auth: Auth = injector.get(Auth);
    let router: Router = injector.get(Router);

  // return a boolean or a promise that resolves a boolean
    return new Promise((resolve) => {
      auth.check()
          .subscribe((result) => {
                    if (result) {
                        resolve(true);
                    } else {
                        router.navigate(['/Login']);
                        resolve(false);
                    }
                });
  });
};

appInjector.ts

import {Injector} from 'angular2/core';

let appInjectorRef: Injector;
export const appInjector = (injector?: Injector):Injector => {
    if (injector) {
      appInjectorRef = injector;
    }

    return appInjectorRef;
};

somecomponent.ts

import {Component, View,ViewChild} from 'angular2/core';
import {CanActivate} from 'angular2/router';
import {isLoggedIn} from './isLoggedIn';

@Component({
  selector: 'some',
  template: 'some text'
})
@CanActivate((next: ComponentInstruction, previous: ComponentInstruction) => {
  return isLoggedIn(next, previous);  // this will tell whether user is loggedIn or not. 
})
export class Protected {
}

boot.ts

.
.
import { provide, ComponentRef } from 'angular2/core';
import { appInjector } from './app-injector';
.
.
bootstrap(AppComponent, [...]).then((appRef: ComponentRef) => {
  // store a reference to the application injector
  appInjector(appRef.injector);
});

There are two ways to restrict access Custom Router Outlet and CanActivate Decorator shown and implemented in this great article Authentication in Angular 2

Upvotes: 2

Günter Zöchbauer
Günter Zöchbauer

Reputation: 658027

I think extending RouterOutlet is a common way to achive this

Example posted a while ago in Gitter by CaptainCodeman (not tested myself yet)

  import {Directive, Injector, Attribute, ElementRef, DynamicComponentLoader} from 'angular2/core';
  import {Router, RouteData, RouterOutlet, RouteParams, Instruction, ComponentInstruction} from 'angular2/router';

  /*
    Example implementation

    Given a route:
    @RouteConfig([
    { path: '/thing/:id', component: ThingComponent, name: 'Thing', data: { public: false, roles:['thinger'] } }
    ])

    authorize(instruction: ComponentInstruction):boolean {
      // simplest case - route is public
      if (<boolean>instruction.routeData.data['public']) {
        return true;
      }

      // if not public then we at least need an authenticated user
      if (this.isAuthenticated()) {
        var routeRoles = <any[]>instruction.routeData.data['roles'];
        var userRoles = <string[]>this.roles();

        // no roles required for route = user just needs to be authenticated
        var authorized = routeRoles.length === 0 || routeRoles.some(routeRole => userRoles.indexOf(routeRole) >= 0);

        return authorized;
      }

      return false;
    }
  */
  export abstract class IAuthService {
    abstract isAuthenticated():boolean;
    authorize(instruction: ComponentInstruction, params:any):boolean {
      // authorized if route allows public access or user is authenticated
      return this.isAuthenticated() || <boolean>instruction.routeData.data['public']
    }
  }
@Directive({selector: 'secure-outlet'})
  export class SecureRouterOutlet extends RouterOutlet {
    signin:string;
    unauthorized:string;
    injector:Injector;

    private parentRouter: Router;
    private authService: IAuthService;

    constructor(_elementRef: ElementRef, _loader: DynamicComponentLoader,
                _parentRouter: Router, @Attribute('name') nameAttr: string,
                authService:IAuthService,
                injector:Injector,
                @Attribute('signin') signinAttr: string,
                @Attribute('unauthorized') unauthorizedAttr: string) {
      super(_elementRef, _loader, _parentRouter, nameAttr);
      this.parentRouter = _parentRouter;
      this.authService = authService;
      this.injector = injector;
      this.signin = signinAttr;
      this.unauthorized = unauthorizedAttr;
    }

    activate(nextInstruction: ComponentInstruction): Promise<any> {
      var params = this.getAllRouteParams(this.injector);
      var isAuthorized = this.authService.authorize(nextInstruction, params);

      if (isAuthorized) {
        return super.activate(nextInstruction);
      }

      if (this.authService.isAuthenticated()) {
        var ins = this.parentRouter.generate([this.unauthorized]);
        return super.activate(ins.component);
      } else {
        var ins = this.parentRouter.generate([this.signin,{url:location.pathname}]);
        return super.activate(ins.component);
      }
    }

    reuse(nextInstruction: ComponentInstruction): Promise<any> {
      return super.reuse(nextInstruction);
    }

    getAllRouteParams(injector) {
      let params = null;
      while(injector) {
        const routeParams = injector.getOptional(RouteParams);
        if (routeParams) {
          if (params === null) {
            params = {};
          } else {
            params = Object.create(params);
          }

          Object.assign(params, routeParams.params);
        }
        injector = injector.parent;
      }
      return params;
    }
  }

Upvotes: 4

Thierry Templier
Thierry Templier

Reputation: 202306

If you use routing (and it seems to be the case since you say: "on every page change"), you can leverage several things:

  • Create a custom router-outlet (a sub class of RouterOutlet) that checks authentication with its activate method is called. In this case, you can have something global. Something like that:

    @Directive({
      selector: 'auth-outlet'
    })
    export class AuthOutlet extends RouterOutlet {
      (...)
    
      activate(oldInstruction: ComponentInstruction) {
        var url = this.parentRouter.lastNavigationAttempt;
        if (isAuthenticated()) {
          return super.activate(oldInstruction);
        } else {
          (...)
        }
      }
    }
    

    See this question for more details:

  • Leverage the CanActivate decorator to check is a component can be activated or not. In your case, you can execute authentication checking at this level.

  • Something could also be done at the RouterLink level to show / hide route links. In this case, you can apply roles on these links based on related route configuration and current user hints. See this question for more details:

This can be also handled within an HTTP interceptor (a class that extends the Http one). In this case, when a request is executing, you can plug some authentication checks:

@Injectable()
export class CustomHttp extends Http {
  constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
    super(backend, defaultOptions);
  }

  request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    console.log('request...');
    if (isAuthenticated()) {
      return super.request(url, options).catch(res => {
        // do something
      });        
    } else {
      // Redirect to login page
      // Or throw an exception: return Observable.throw(new Error(...));
    }
  }

  (...)
}

See this question for more details:

Upvotes: 8

Related Questions