0

I'm having issues with Angular routing that don't seem to make much sense. With the following set-up, this outcome occurs...

  • App loads at path /
  • Auth guard runs
  • Auth guard returns false as there is not yet a JWT in storage
  • Redirection to /login works as expected.

However...

  • Navigate directly to /activate (Route in the account module)
  • Console logs that the auth guard is being run (Which shouldn't happen)
  • Redirected to /login

I'm struggling to understand why auth guard is running for the /activate route when it's not a child of the dashboard layout.

App routes

 {
    path: '',
    component: DashboardLayoutComponent,
    canActivate: [AuthGuard],
    canActivateChild: [DashboardGuard],
    children: [
      {
        path: 'schedule',
        loadChildren: () =>
          import('@libs/schedule').then(
            i => i.ScheduleModule
          ),
        data: {
          breadcrumb: 'Schedule'
        }
      },
      // Other feature modules omitted
      {
        path: '',
        redirectTo: '/schedule',
        pathMatch: 'full'
      }
    ]
  }

Account Routes

  { path: 'login', component: LoginComponent },
  { path: 'activate', component: ActivateComponent }

Auth Guard

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(
    private jwtService: JwtService,
    private router: Router,
    private accountService: accountService
  ) { }

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {

    console.log('Auth Guard Start'); // <-- This appears in console

    return this.jwtService.getToken().pipe(
      map(token => {
        if (!token) {
          this.router.navigate(['/login']);
          return false;
        }
        // Attempt to populate the user using the token.
        this.accountService.populate();
        return true;
      }),
      take(1)
    );
  }
}

App Module

@NgModule({
  declarations: [AppComponent, DashboardLayoutComponent],
  imports: [
    // .. other modules omitted
    AccountModule,
    AppRoutingModule
  ],
  providers: [AuthGuard, DashboardGuard],
  bootstrap: [AppComponent]
})
export class AppModule { }

Additional information

This happens when running in production mode only.

Augury reports that /login and /activate are both siblings of the / route. https://i.stack.imgur.com/2LDY4.jpg

@angular/core: 8.2.6

@angular/router: 8.2.6

Ben Brookes
  • 419
  • 4
  • 15

2 Answers2

0

Actually i think, that clue for your problem is as follows:

You adding routes for accounting modules fisrt, and of course in that module you marking them as forChild. Then, you added main AppModuleRouts. After your application was compiled and run - etire RouterTree includes all possible paths. So that, routes from AccountModule actually became a child routes. So, as far as you applied canActivate: [AuthGuard] on an empty route - the most general of it could be - it fires every time.

Imme
  • 21
  • 4
  • Only one router should be imported for root. Account is a feature module and should be able to handle it's routing as I have it set up. – Ben Brookes Feb 06 '20 at 12:07
  • And I even do not want to argue with that. But the reason which cause your guard fires every time is written above. – Imme Feb 06 '20 at 12:12
  • So, as @ritaj has already written all your feature module routes became child.... – Imme Feb 06 '20 at 12:14
  • But not a child of a route defined in my app routing module. So this still doesn't answer the question. Augury shows that dashboard, login and activate are all siblings. So what is it you are proposing? – Ben Brookes Feb 06 '20 at 12:17
  • The most brief proposal in your case is to move App routes on more level lower. It may be not clearest one but fastest for shure. – Imme Feb 06 '20 at 12:27
  • Please see here - https://imgur.com/a/CJyKu8C Login and activate are siblings, not children. – Ben Brookes Feb 06 '20 at 12:32
  • I have to admit you were right. But, when i have created an app with similar routing scheme i've not met such a behaviour neither in dev nor in prod modes. It seems you need to share your app (part of it of course) so we can investigate details – Imme Feb 06 '20 at 13:08
  • Thank for your help all the same. – Ben Brookes Feb 06 '20 at 13:11
0

This issue was actually caused by my ngrx-router implementation where I was defining its initial state.

Originally initial state was set in the following way as suggested here - https://github.com/ngrx/platform/issues/835#issuecomment-369592809

export const routerInitialState: fromRouter.RouterReducerState<RouterStateUrl> = {
  state: {
    url: window.location.pathname,
    queryParams: getQueryParams(window.location.search.substring(1)),
    params: {}
  },
  navigationId: 0
}

This will not work in production builds. Navigating directly to a route like /activate, for example, causes a discrepancy between router state and angular router state, cancelling the navigation.

The correct way top set the initial router state is as follows...

export const routerInitialState: fromRouter.RouterReducerState<RouterStateUrl> = {
  state: {
    url: '/',
    queryParams: {},
    params: {}
  },
  navigationId: 0
}

With initialNavigation: 'enabled' in app-routing.module, the router state is updated at the earliest possible point and is synced with the angular router.

Ben Brookes
  • 419
  • 4
  • 15