63

I would like to automatically route to a login page if the user is not logged in.

app.module.ts

import { RouterModule, Routes } from '@angular/router';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { DashBoardComponent} from './dashboard/dashboard.component';
import { NotFoundComponent } from './not-found/not-found.component';

const APPROUTES: Routes = [
  {path: 'home', component: AppComponent},
  {path: 'login', component: LoginComponent},
  {path: 'dashboard', component: DashboardComponent},
  {path: '**', component: NotFoundComponent}
];

@NgModule({
  declarations: [
    AppComponent,
    LoginComponent,
    DashboardComponent
    NotFoundComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    MaterialModule.forRoot(),
    RouterModule.forRoot(APPROUTES)
  ],
  providers: [],
  bootstrap: [AppComponent]
})

If the user isn't logged in, the LoginComponent should load, otherwise the DashboardComponent.

Community
  • 1
  • 1
Rafael Moura
  • 1,247
  • 2
  • 14
  • 26

2 Answers2

135

Here are 3 ways to do what you asked, from least preferred to favorite:

Option 1. Imperatively redirect the user in AppComponent

@Component({
  selector: 'app-root',
  template: `...`
})
export class AppComponent {
  constructor(authService: AuthService, router: Router) {
    if (authService.isLoggedIn()) {
      router.navigate(['dashboard']);
    }
  }
}

Not very good. It's better to keep the "login required" information in the route declaration where it belongs.

Option 2. Use a CanActivate guard

Add a CanActivate guard to all the routes that require the user to be logged in:

const APPROUTES: Routes = [
  {path: 'home', component: AppComponent, canActivate:[LoginActivate]},
  {path: 'dashboard', component: DashBoardComponent, canActivate:[LoginActivate]},
  {path: 'login', component: LoginComponent},
  {path: '**', component: NotFoundComponent}
];

My guard is called LoginActivate.

For it to work I must add the guard to my module's providers.

And then I need to implement it. In this example I'll use the guard to redirect the user if they're not logged in:

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

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean>|Promise<boolean>|boolean {
    if (!this.authService.isLoggedIn()) {
      this.router.navigate(['login']);
    }
    return true;
  }
}

Check out the doc about route guards if this doesn't make sense: https://angular.io/docs/ts/latest/guide/router.html#guards

This option is better but not super flexible. What if we need to check for other conditions than "logged in" such as the user permissions? What if we need to pass some parameter to the guard, like the name of a role "admin", "editor"...?

Option 3. Use the route data property

The best solution IMHO is to add some metadata in the routes declaration to indicate "this route requires that the user be logged in".

We can use the route data property for that. It can hold arbitrary data and in this case I chose to include a requiresLogin flag that's either true or false (false will be the default if the flag is not defined):

const APPROUTES: Routes = [
  {path: 'home', component: AppComponent, data:{requiresLogin: true}},
  {path: 'dashboard', component: DashBoardComponent, data:{requiresLogin: true}}
];

Now the data property in itself doesn't do anything. But I can use it to enforce my "login required" logic. For that I need a CanActivate guard again.

Too bad, you say. Now I need to add 2 things to each protected route: the metadata AND the guard...

BUT:

  • You can attach the CanActivate guard to a top-level route and it will be executed for all of its children routes [TO BE CONFIRMED]. That way you only need to use the guard once. Of course, it only works if the routes to protect are all children of a parent route (that's not the case in Rafael Moura's example).
  • The data property allows us pass all kinds of parameters to the guard, e.g. the name of a specific role or permission to check, a number of points or credits that the user needs to possess to access the page, etc.

Taking these remarks into account, it's best to rename the guard to something more generic like AccessGuard.

I'll only show the piece of code where the guard retrieves the data attached to the route, as what you do inside the guard really depends on your situation:

@Injectable()
export class AccessGuard implements CanActivate {
  canActivate(route: ActivatedRouteSnapshot): Observable<boolean>|Promise<boolean>|boolean {
    const requiresLogin = route.data.requiresLogin || false;
    if (requiresLogin) {
      // Check that the user is logged in...
    }
  }
}

For the above code to be executed, you need to have a route similar to:

{
  path: 'home',
  component: AppComponent,
  data: { requiresLogin: true },
  canActivate: [ AccessGuard ]
}

NB. Don't forget to add AccessGuard to your module's providers.

bvdb
  • 22,839
  • 10
  • 110
  • 123
AngularChef
  • 13,797
  • 8
  • 53
  • 69
  • 2
    ok friend I'm go estudy and apply this way and to give a feed back ok thanks – Rafael Moura Feb 06 '17 at 11:01
  • 1
    How do option 2 & 3 support forwarding the user onto the dashboard if the user is already logged in? As I understand, the guards can prevent accessing a component, but option 1 allows for redirecting if the user is already authorized – Graham S Apr 06 '17 at 04:03
  • The guards can return true or false to grant/prevent access but they can also redirect the user based on your criteria. – AngularChef Apr 06 '17 at 08:51
  • 1
    I do not approve your option 2, setting up guard on Home page. Because if you use pre rendering of your home page, you can't check if user is log in, and you will get flickering (homepage pre viewing > navigating page returning by guard). Guard is used to restrict access page, not for redirect to another page. – Nicolas Law-Dune Apr 07 '17 at 06:58
  • 3
    Duly noted! ;-) FYI the official docs have several [examples](https://angular.io/docs/ts/latest/guide/router.html#teach-authguard-to-authenticate) of guards redirecting the user. – AngularChef Apr 07 '17 at 10:39
  • @NicolasLaw-Dune: In fact, angular documentation shows the returning by guard, and if you think... it has sense. – Gabriel Andrés Brancolini Jun 28 '17 at 12:32
  • I followed option 2 which is working nicely, but if user is not logged in and tries to access any secure route by directly hitting onto Url bar at that time angular rendering index html page and nav bars for 1 or 2 second and later it redirects to login page. How can we prevent from rendering index html if user is not loged in? – Rahul Mistry Oct 05 '18 at 05:00
  • What is AuthService in your exmaples? – Muhammad bin Yusrat Jan 14 '19 at 05:44
  • 2
    I do fully agree with your preferences, with the one exception that I would prefer option 1 if the whole app should be only accessable to logged-in users. – Janis Jansen Sep 24 '19 at 11:25
  • I took option #3 approach and it worked quite well. I agree with the `data` to control the access. Thanks @AngularChef – Senthil Jul 14 '20 at 14:51
10

You can also do something like this:

{
  path: 'home',
  component: getHomeComponent(),
  data: { requiresLogin: true },
  canActivate: [ AccessGuard ]
}

And then:

export function getHomeComponent(): Type<Component> {
  if (User.isLoggedIn) {
    return <Type<Component>>HomeComponent;
  }
  else{
    return <Type<Component>>LoginComponent;
  }
}