Angular Routing demo: automatically route away from protected area if logged out (refire canActivate guard)

Viewed 144

See official Angular docs about routing: https://angular.io/guide/router

And its demo: https://stackblitz.com/angular/pbqlrdpbpvx

The demo demonstrates an admin section which may only be accessed if the auth service state isloggedIn returns true. The section is protected with an canActivate auth-guard.

By nature, the canActivate guard will only fire one time when you try to access the page. And correctly routes you to the login process if your auth service state ìsloggedIn returns false.

However, I need my canActivate guard to fire every time if there is a value change of my auth service state ìsloggedIn, to ensure that if the auth status turns false by itself (i.e. if I use expiry date or something at a later point), to have the user automatically routed off the protected section. He shall not remain on the protected area page if out of all sudden the value changes to false.

Steps to reproduce by adding a simple logout button on the top nav:

  1. Extend demo (https://stackblitz.com/angular/pbqlrdpbpvx) with logout button on top:

app.component.ts:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { slideInAnimation } from './animations';
import { AuthService } from './auth/auth.service';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'],
  animations: [ slideInAnimation ]
})
export class AppComponent {  
  constructor(public authService: AuthService) {}

  getAnimationData(outlet: RouterOutlet) {
    return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
  }

  logout() {
    this.authService.logout();
  }
}

app.component.html:

<h1 class="title">Angular Router</h1>
<nav>
  <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
  <a routerLink="/superheroes" routerLinkActive="active">Heroes</a>
  <a routerLink="/admin" routerLinkActive="active">Admin</a>
  <a routerLink="/login" routerLinkActive="active">Login</a>
  <a (click)="logout()" *ngIf="authService.isLoggedIn">Click To Logout</a>
  <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>
</nav>
<div [@routeAnimation]="getAnimationData(routerOutlet)">
  <router-outlet #routerOutlet="outlet"></router-outlet>
</div>
<router-outlet name="popup"></router-outlet>
  1. Click on the login button and login - you will be routed to the admin section and the logout button on the top nav will show.

  2. Click on the Logout button and see that the view remains unchanged. You are still on the admin section.

Intended behaviour:

If the auth service's isLoggedIn state turns false, the user shall automatically be redirected to the login page. As such, the anActivate guard should fire automatically on value change.

Reason for current behaviour:

The auth guard's canActivate which determines if you can access the page or not only is checked against only once in the beginning when you enter the page.

Current solution:

Extending the click functionality of the logout-button with a route-call to the login section. This however does not cover all the use cases.

Thanks in advance.

3 Answers

I think you are looking for a change listener on your isLoggedIn Propertie. It may work with ngOnChanges, which is described here. I also found some examples for you.

Have you tried calling Observable from Auth Guard,

constructor(private authService:AuthService, private router:Router) { }

    canActivate(next:ActivatedRouteSnapshot, state:RouterStateSnapshot) {
        return this.authService.isLoggedIn().map( v => {
            if (v) {
                return true;
            }
        }).catch(() => {
            this.router.navigate(['/login']);
            return Observable.of(false);
        });
    } 

Having the AuthGuard handle it in this way would produce side effects, since you can use the AuthGuard for multiple routes (the most likely scenario), the redirection would fire once for every route you have entered at least once.

Instead, just inject Router into your auth service and put the redirect logic inside the auth.logout() method, by using router's navigate(...) method.

Related