Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular: Run canActivate on each route change

I got stuck recently with Angular route guards. CanActive runs only once when the page is loaded and does not run on route change within the guarded route. I think this was changed because it used to run on each change. From what I read in forums, I should use CanActivateChild. The thing is, our application consists of several modules, that have several route descendants and when I use CanActivateChild in root module, it is called several times when changing the route.

I find it dumb to assign a guard to each child because, for AppModule, those lazy loaded child modules should be just 'Black Boxes' and I wanted to define that all those modules should be guarded.

export const routes: Routes = [
  {
    path: '404',
    component: NotFoundComponent
  },
  {
    path: '',
    canActivate: [AuthGuard],
    component: FullLayoutComponent,
    data: {
      title: 'Home'
    },
    children: [
      {
        path: 'administration',
        loadChildren: './administration/administration.module#AdministrationModule'
      },
      {
        path: 'settings',
        loadChildren: './settings/settings.module#SettingsModule'
      }
    ]
  },
  {
    path: '',
    loadChildren: './account/account.module#AccountModule'
  },
  {
    path: '**',
    redirectTo: '404'
  }
];

Is there any solution to this? Or do You find this as 'not an issue' regarding security?

Thank You all.

like image 840
Filip Matys Avatar asked Oct 18 '17 07:10

Filip Matys


People also ask

Can we have multiple Auth guard in Angular?

We can use multiple route guards and then the route will only be accessible when all route guards return true. That's it!

How does canActivate work in Angular?

The canActivate method returns a boolean indicating whether or not navigation to a route should be allowed. If the user isn't authenticated, they are re-routed to some other place, in this case a route called /login . Now the guard can be applied to any routes you wish to protect.

What is the difference between canActivate and CanDeactivate?

CanActivateChild - Decides if children routes of a route can be activated. CanDeactivate - Decides if a route can be deactivated.


2 Answers

Faced the same issue and all I was able to find on issue are few closed issues on Github with Angular devs statements that such behavior "is by design".

So what I ended up doing is subscribing on navigation events in app.component and firing AuthGuard check there:

constructor(
  private router: Router,
  private route: ActivatedRoute,
  private authGuard: AuthGuard,
) {}

ngOnInit() {
  this.router.events
    .subscribe(event => {
      if (event instanceof RoutesRecognized) {
        this.guardRoute(event);
      }
    }));
}

private guardRoute(event: RoutesRecognized): void {
  if (this.isPublic(event)) {
    return;
  }

  if (!this.callCanActivate(event, this.authGuard)) {
    return;
  }
}

private callCanActivate(event: RoutesRecognized, guard: CanActivate) {
  return guard.canActivate(this.route.snapshot, event.state);
}

private isPublic(event: RoutesRecognized) {
  return event.state.root.firstChild.data.isPublic;
}

AuthGuard is rather standard:

@Injectable()
export class AuthGuard implements CanActivate{

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

  canActivate(): Promise<boolean> {
    return this.auth.isLoggedInPromise()
      .then(isLoggedIn => {
        if (!isLoggedIn) {
          this.router.navigate(["/login"]);
        }
        return isLoggedIn;
      });
    }
  }

And public routes should be configured like this:

{
  path: "login",
  component: LoginComponent,
  data: { isPublic: true }
}

The plus of such implementation is that everything is protected by default and public route should be configured explicitly, which will reduce the possibility of leaving some routes unprotected. Will also refactor this into some kind of service, to be able to use it across multiple apps.

Inspired by this answer.

like image 106
liri2006 Avatar answered Sep 20 '22 03:09

liri2006


The issue with subscribing to router events is that the navigation has already been started and the history has been updated, which makes it hard to prevent the navigation in a reliable way as a guard does.

But Angular has learned to provide you with a way to configure how guards and resolvers should behave directly in your routes.ts:

export const routes: Routes = [
  {
    path: '404',
    component: NotFoundComponent
  },
  {
    path: '',
    canActivate: [AuthGuard],
    runGuardsAndResolvers: 'always',
    children: [
       ....
    ]
  }
]

Here's the docs: https://angular.io/api/router/RunGuardsAndResolvers

There's a nice blogpost explaining your options: https://juristr.com/blog/2019/01/Explore-Angular-Routers-runGuardsAndResolvers/

like image 31
nuclearglow Avatar answered Sep 24 '22 03:09

nuclearglow