Angular 4 Multiple Guards - Execution Sequence

Akul Narang picture Akul Narang · May 10, 2017 · Viewed 17.8k times · Source

I have 2 guards, AuthGuard and AccessGuard in the application. AuthGuard protects all the pages as the name suggests and stores the session object in the GlobalService and AccessGuard depends on the some access data in session object stored by AuthGuard in GlobalService.

Problem arises when AuthGuard returns an Observable and then simultaneously AccessGuard executes to check for session object which has not yet arrived and the code breaks. Is there any other way I can restrict the execution of AccessGuard until the session object arrives or any other work around to break this race condition?

#Note I have not merged the AccessGuard logic to AuthGuard as only some of the routes need to be checked for access while all other needs authentication. For example, Accounts page and DB page are accessible to all but User Managements and Dashboard need external access parameters that come from session object

export const routes: Routes = [
  {
    path: 'login',
    loadChildren: 'app/login/login.module#LoginModule',
  },
  {
    path: 'logout',
    loadChildren: 'app/logout/logout.module#LogoutModule',
  },
  {
    path: 'forget',
    loadChildren: 'app/forget/forget.module#ForgetModule',
  },{
    path: 'reset',
    loadChildren: 'app/reset/reset.module#ResetModule',
  },

    path: 'pages',
    component: Pages,
    children: [
      { path: '', redirectTo: 'db', pathMatch: 'full' },
      { path: 'db', loadChildren: 'app/pages/db/db.module#DbModule' },
      { path: 'bi', loadChildren: 'app/pages/dashboard/dashboard.module#DashboardModule', canActivate:[AccessableGuard] },
      { path: 'account', loadChildren: 'app/pages/account/account.module#AccountModule' },
      { path: 'um', loadChildren: 'app/pages/um/um.module#UserManagementModule', canActivate:[AccessableGuard] },
    ],
    canActivate: [AuthGuard]
  }
];

export const routing: ModuleWithProviders = RouterModule.forChild(routes);

#EDIT: Adding the Guard Codes

AuthGuard:

canActivate(route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{
  return new Observable<boolean>( observer => {
    this._dataService.callRestful('POST', params.SERVER.AUTH_URL + urls.AUTH.GET_SESSION).subscribe(
        (accessData) => {
          if (accessData['successful']) {
            observer.next(true);
            observer.complete();
            console.log("done");
          }
          else {
            observer.next(false);
            observer.complete();
          }
        });
  });
}

AccessableGuard:

canActivate(route:ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean{        
if(this._dataService.getModulePermission(route.routeConfig.path.toUpperCase()) < 2){
        return false;
      }
      return true;
    }

#NOTE: _dataService is GlobalService that stores the Access Permissions from AuthGuard.

Answer

seangwright picture seangwright · Sep 8, 2018

I chose a different path --- Nesting my guards and making them dependencies of each other.

I have a RequireAuthenticationGuard and a RequirePermissionGuard. For most routes they need to both run but there is a specific order I require.

The RequireAuthenticationGuard depends on my authN services to check if the current session is authenticated.

The RequirePermissionGuard depends on my authZ services to check if the current session is authorized for a route.

I add the RequireAuthenticationGuard as a constructor dependency of RequirePermissionGuard and only begin checking permissions if authentication has been determined.

require-authentication.guard.ts

constructor(
    private userSessionSerivce: UserSessionService) {}

canActivate(
    _route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot,
): Observable<boolean> {
    return this.validateAuthentication(state.url);
}

require-permission.guard.ts

constructor(
    private permissionService: PermissionService,
    /**
    * We use the RequireAuthenticationGuard internally
    * since Angular does not provide ordered deterministic guard execution in route definitions
    *
    * We only check permissions once authentication state has been determined
    */
    private requireAuthenticationGuard: RequireAuthenticatedGuard,
) {}

canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot,
): Observable<boolean> {
    const requiredPermissions: Permission[] = next.data.permissions || [];

    return this.requireAuthenticationGuard
        .canActivate(next, state)
        .pipe(
            mapTo(this.validateAuthorization(state.url, requiredPermissions)),
        );
}