9

I have an angular 2 application in which I need to be authenticated on every page. So I have implemented a custom RouterOutlet to confirm I am logged in on every page change.

@Directive({
   selector: 'auth-outlet'
})
export class AuthOutlet extends RouterOutlet {
   publicRoutes: any;
   private parentRouter: Router;
   private authService: AuthService;
   constructor(_elementRef: ElementRef, 
               _loader: DynamicComponentLoader, 
               _parentRouter: Router,
               @Attribute('name') nameAttr: string, 
               _authService: AuthService) {

      super(_elementRef, _loader, _parentRouter, nameAttr);
      this.parentRouter = _parentRouter;
      this.authService = _authService;
      this.publicRoutes = {
          'Login': true
      };
  }

  activate(oldInstruction: ComponentInstruction) {
      var url = this.parentRouter.lastNavigationAttempt;
      console.log('attemping to nav');
      if (!this.publicRoutes[url] && !this.authService.loggedIn){
          var newInstruction = new ComponentInstruction('Login', [], new RouteData(), Login, false, 1);
          return super.activate(newInstruction);
      } else {
          return super.activate(oldInstruction);
      }
   }
}

Here is a working code: http://plnkr.co/edit/YnQv7Mh9Lxc0l0dgAo7B?p=preview

Is there a better way to intercept route changes and redirect for login when the user is not authenticated?

Juan Sánchez
  • 1,014
  • 2
  • 15
  • 29
Karl L
  • 260
  • 4
  • 9
  • 1
    Superb!! What else you need !! – micronyks Feb 13 '16 at 06:58
  • Well, for one thing you aren't supposed to new up a ComponentInstruction. So this already has problems. Plus it has issues if you're in a child route that doesn't know about the Login route. (I worked on this issue with the poster) – Joseph Eames Feb 13 '16 at 19:56
  • If someone deep links to the page, the server gets involved and you do an auth check on the server. If someone is authorized, they get into your app. Once in your app, they can move about freely. The browser and js should not be doing auth checks here, the server should. – John Papa Feb 13 '16 at 23:19
  • That sounds so simple John. yet it's not. Doesn't handle client-side timeouts. Doesn't handle a logout on the client. Doesn't handle apps with various auth levels. or apps where some of the app is pre-login, some post. Should I lazy load the half of the app behind login the moment then login? And how do I un-download the half behind login when they logout? – Joseph Eames Feb 14 '16 at 01:02
  • Do you get anyvalue , `var url = this.parentRouter.lastNavigationAttempt` ,in url?? – micronyks Feb 24 '16 at 10:05

3 Answers3

9

For anyone that finds this, the answer now in Angular 2 is to use "Guards" as part of the new Router. See Angular 2 documentation:

https://angular.io/docs/ts/latest/guide/router.html#!#guards

A basic guard just implements "CanActivate", and could work as follows:

import {Injectable} from "@angular/core";
import {CanActivate, Router} from "@angular/router";
import {AuthService} from "../services/auth.service";

@Injectable()
export class AuthGuard implements CanActivate {
    constructor(private authService:AuthService, private router:Router){}

    canActivate(){
        if(this.authService.isAuthenticated())
            return true;

        this.router.navigate(["/login"]);
        return false;
    }
}

As you can see in this example I have an AuthService running somewhere else (implementation isn't important) which can tell the guard if the user has been authenticated. If they have, return true and the navigation happens as usual. If they have not, we return false and redirect them to the login screen.

John Ackerman
  • 1,041
  • 1
  • 9
  • 16
  • Hi John, welcome to SO and thanks for your answer. Code only answers are discouraged because the link can change over time and become invalidated - therefore it is much better to provide a complete answer that can stand here on its own, with the link as a reference. Would you mind editing your answer with this in mind? Thanks again! – Tim Malone Jun 29 '16 at 00:59
  • @JohnAckerman But the disadvantage to this approach compared to OP's one is that we have to mention this guard for all the auth required paths so duplication and using OP's code we can also store the last attempted route and then use to redirect the user once login is successful. – lbrahim Aug 30 '16 at 12:45
  • @Ibrahim not really. If you have a whole section (module?) of a site that needs to be behind the guard, then you should structure your routes accordingly. You only need the guard at the parent level. If you define an /admin route with children, the only the admin route needs the guard and the children inherit it. – John Ackerman Sep 01 '16 at 17:12
3

Here's an updated example of using an AuthGuard with Angular 2 RC6.

Routes with home route protected by AuthGuard

import { Routes, RouterModule } from '@angular/router';

import { LoginComponent } from './login/index';
import { HomeComponent } from './home/index';
import { AuthGuard } from './_guards/index';

const appRoutes: Routes = [
    { path: 'login', component: LoginComponent },

    // home route protected by auth guard
    { path: '', component: HomeComponent, canActivate: [AuthGuard] },

    // otherwise redirect to home
    { path: '**', redirectTo: '' }
];

export const routing = RouterModule.forRoot(appRoutes);

AuthGuard redirects to login page if user isn't logged in

import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';

@Injectable()
export class AuthGuard implements CanActivate {

    constructor(private router: Router) { }

    canActivate() {
        if (localStorage.getItem('currentUser')) {
            // logged in so return true
            return true;
        }

        // not logged in so redirect to login page
        this.router.navigate(['/login']);
        return false;
    }
}

For the full example and working demo you can check out this post

Jason Watmore
  • 4,521
  • 2
  • 32
  • 36
0

You can also use CanActivate, however direct DI is not supported at the moment. Here is a nice workaround tho.

Good luck.