Solvedangular router CanLoad Route guard broken for common use case

there is an issue filed but it is listed as a feature request and i think it needs to be more of a priotity:
#12411

I'm submitting a ... (check one with "x")

[ x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
an angular app has a feature module that should be lazy loaded , a CanLoad guard needs to deal with the use case that the caller is not yet authenticated and needs to get a token before the guard can determine how the request will be handled.
with a call to https://github.com/IdentityModel/oidc-client-js that uses https://github.com/IdentityModel/oidc-client-js
a call to the STS server is used to return back to the angular app with a token.
Can Load does not have the same access that Can Activate has to the router.navigate() data so the can load fails to get the user to the route.

Expected behavior

CanLoad should have the navigate route available in some reasonable form.
in CanActivate we can access the url and pass it to the STS server and the STS server passes that back to the angular app and everything works.
CanLoad needs to be able to work in the same way, if the router data can not be the final url then we need some other "state" object that we can get and a way to load that state back into the router when the STS server returns the user token to the app.

this STS server case happens with any third party auth like Google,Facebook,Microsoft or Twitter

Minimal reproduction of the problem with instructions
this needs several items to repro, i can't give a sample right now but the parts are this:
https://github.com/IdentityModel/oidc-client-js

and an angular app that has a feature module that should not be loaded unless the user has a token that grants access to a view inside the feature module.

canLoad needs to make a call this.mgr.signinRedirect({ data: state })
and the oidc manager 'mgr' returns to finish the callback and we then have code like this:
this.mgr.signinRedirectCallback().then((user) => { // console.log("signed in", user); this.loggedIn = true; this.currentUser = user; this.userLoadededEvent.emit(user); // // if the login request was for a given view // we saved that before we redirected the user to the login page. // now we need to restore the view or route to the view // restore if they were already there, route to it if needed authentication to // get to that view // // console.log( " user.state = " , user.state ); if( user.state){ this.router.navigate([ user.state ]); }else{ this.router.navigate(['']); } }).catch( (err) => { console.log(err); });

What is the motivation / use case for changing the behavior?
so that lazy loading of a feature module will work when using a third party authentication server via OIDC/ Oauth 2.

Please tell us about your environment:
WIndows Server 2012, IIS 8.5 but this issue will happen on any web server.

  • Angular version: 2.0.X
    4.xxx current version as of a week ago.

  • Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
    tested in chrome as of now, should happen with any browser.

  • Language: [all | TypeScript X.X | ES6/7 | ES5]
    TypeScript 2.3.2

  • Node (for AoT issues): node --version =
    6.9.1

19 Answers

✔️Accepted Answer

My Current workaround (ng 6 with rxjs 6)

export class AuthGuard implements CanLoad{
  constructor( private router: Router ) {}
  public canLoad(route: Route): boolean {
    if(this.isAuthenticated){
      return true;
    }
    this.router.events.pipe(first(_ => _ instanceof NavigationCancel)).subscribe((event: NavigationCancel) => {
      // event.url is the target route the user was attempting to reach
      this.redirectToLogin(event.url);
    });
   // returning false cancels the navigation triggering the code above
    return false;
  }

  private isAuthenticated(): boolean {
   ...
  }
  private redirectToLogin(redirect:string):void {
     this.router.navigate(['/login'], {
      queryParams: {
        redirect: redirect
      }
    })
  }
}

Other Answers:

@figuerres This bug negates the usefulness of canLoad guard in many cases.

Common case:

  1. The user bookmarks a URL into a lazy-loaded module that requires auth. (let's call it 1.chunk)
  2. The user sends the url to a friend
  3. Sometimes later, the friend clicks on the link in step 1
  4. canLoad() kicks-in & wants to possibly prevent an unnecessary fetch of 1.chunk
  5. The friend is redirected to the login page to authenticated first
  6. The friend is authenticated, but the system has no recollection of where to redirect the friend to.
  7. Very unhappy friend.

The question is, how can we send a link to a lazy-loaded canLoad-guarded module and expect the recipient of the link to make it to the destination, post authentication, in this example?

Workaround: disable canLoad guards and let canActivate do the guarding. Yes, your friend just paid the price of loading a chuck, and probably decided not to even follow up with auth.

@Toxicable This is not a special case. Bookmarking in this example is a normal and a common use-case.

Here is a more general statement of the problem:

canLoad cannot decide to load a module if the logic needs the route.
for example if the module is Admin and the admin module has a view that it can route to called roles and the navigate route is called /admin/manage/roles
can load does not know that the target is manage/roles it only sees that the admin module is the target.

also if a user enters a full url like https://myapp.com/myaccount/orders/list
can load can use the location url to see what the target path is.
but if you call navigate( '/myaccount/orders/list')
the can load function can not see that path / route

so in any case where the user enters the url can load can see the route but a call from navigate blinds can load to the the same information.

that cripples the utility of can load when the application does navigation.
but can activate does not suffer this route blandness problem.

this difference means that the application has to be designed to not navigate into any feature that needs a can load guard if it needs the route.

there's an even more angular compliant solution without using window.location:

export class AuthnGuard implements CanLoad {
  constructor(private authnService: AuthnService, private router: Router) { }

  canLoad( ): Observable<boolean> | Promise<boolean> | boolean {
    const navigation = this.router.getCurrentNavigation();
    let url = '/';

    if (navigation) {
      url = navigation.extractedUrl.toString();
    }
    this.authnService.storeRedirectUrl(url);

...

After hours of frustration, I came up with a solution that in my case works perfectly.

canLoad(route: Route) {
        if (this.auth.authenticated) {
            return true;
        }

        this.auth.redirectUrl = window.location.pathname;
        this.router.navigate(['/login']);
        return new Promise((resolve) => {
            resolve();
        })
    }

auth is my Authentication Service
auth.redirectUrl is where my Authentication Service navigates the user once authentication succeeds

Basically the Router only knows how to handle true or a Promise.
If you return false it throws one or more errors.

Related Issues:

608
angular Angular5.x lazyLoad problem, undefined is not a function
For others that find this issue via Google as i did: I had the same problem when trying to lazy load...
348
angular Cyclic dependency error with HttpInterceptor
I resolved simply not setting authService in constructor but getting in the intercept function. ...
277
angular Uncaught Error: Can't resolve all parameters for ...
You are missing an @Injectable() annotation on your ApiService Support requests like these should li...
266
angular Force reload/refresh current route with RouteReuseStrategy
Hi If you really need to trick the Router into reloading the component on each routerLink click ...
260
angular Misleading error message "Cannot find a differ supporting object '[object Object]'"
I just ran into the same issue I'm not sure if the recommended solution will work for my case ...
224
angular update 2 to 4 has problem [ts] Property 'map' does not exist on type 'Observable<Response>'.
I met the same problem with the angular cli 6.0.0 and rxjs 6.1.0 And I solved the problem by replaci...
170
angular Angular2 AoT Compiler Errors
pls try /cc @chuckjaz When I try to compile my project with ngc it throws the below error: Error: Er...
152
angular HttpClient.delete() cannot handle a body in its request
It would be great to have body param in .delete() We just migrated our project to HttpClient and for...
140
angular Http - Observable completed function not firing
Third callback haven't been called when error occures ES6 promises hasn't method finally only then a...
133
angular Using multiple components in different modules causing "Type X is part of the declarations of 2 modules" error
as @brandonroberts saids create a shared module like this: then use the SharedModule like this.. ...
112
angular Unsupported platform for fsevents@1.0.14: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
@DzmitryShylovich did you edit package.json only? if npm-shrinkwrap.json is still there please remov...
98
angular Angular v5 ngc compiler: Error encountered in metadata generated for exported symbol 'Subscription'
Got this problem I'm submitting a.. Current behavior Building an Angulary library using 5.0.0-beta.4...
98
angular 404 on route refresh in angular 4
Got it working Just adding .htaccess in root I'm submitting a.. Current behavior I created a new com...
97
angular routerLinkActive not updating when routerLink changed
I have a hack that seems to work After looking at the source code it looks like this.update() is als...
97
angular No provider for HttpClient!
If you are using angular v5 version import HttpClientModule in your app.module.ts after HttpModule T...
81
angular ɵDomAnimationEngine and ɵNoopAnimationEngine module missing in animations 4.2.1
@dubedoy I installed @angular/animations@4.1.3 and it worked again. I'm submitting a .. ...
80
angular Error: Runtime compiler is not loaded in angular6 --prod mode
Do not and i repeat do not import your feature modules in app module and also when addressing featur...
79
angular appending header in HttpHeaders from '@angular/common/http' doesn't work
@trotyl I didn't understand your comment I'm submitting a.. ...
77
angular Can't bind to 'formGroup' since it isn't a known property of 'form'
did you import ReactiveFormsModule? I'm submitting a .. ...
73
angular AOT Compiler requires public properties, while non-AOT allows private properties
@aluanhaddad you have a big misunderstanding in here There is no subset of Typescript in here No one...
73
angular [Bug] angular/elements: Failed to construct 'HTMLElement': Please use the 'new' operator
Hi I have solved this issue by changing the target:es5 in the tsconfig.json to target:es2015 these i...
70
angular Cannot run angular 2+ from file:/// - looks like 'base href="/"' is the issue
Thanks @Markovy @audrummer15 I got it working completely in a fairly complex angular 2 app with mult...
69
angular HttpClient fails to parse an empty 200 response in IE11
For my error I was able to fix the problem by setting the responseType: to 'text' in the options ...
66
angular Function calls are not supported in decorators when fullTemplateTypeCheck is not specified and @dynamic has no effect
Regarding Ward's repro: @wardbell The build will succeed / fail depending on the combination of angu...
62
angular error TS2451: Cannot redeclare block-scoped variable 'ngDevMode'
had to add this line in the main tsconfig I'm submitting a.. ...
62
angular Problem with ngFor
Wouldn't [(ngModel)]=testItems[i] do the trick? I think that the error is saying that you can assign...
61
angular Issue with importing Observable from rxjs/Rx (import-blacklisted)
You shouldn't import from 'rxjs' or 'rxjs/Rx' since either import will import the whole of rxjs whic...
53
angular [RC5]: Minified bundle breaks
@robertoforlani Hopefully someone will have time to write a comprehensive explanation soon In the me...
53
angular router-outlet is appending rather than replacing when using BrowserAnimationsModule
Trying this solved the problem for me: this.zone.run(() => { this.router.navigate(['/main']); }); Re...
52
angular Lazy loaded module in named outlet throws error
We have this Where proxy route component is simply [x] bug report [ ] feature request [ ] support re...
52
angular IVY Error NG6002: Appears in the NgModule.imports of AppModule, but could not be resolved to an NgModule class
Not sure this will provide anyone relief or assist with figuring out what the root cause is but clea...
51
angular How to run angular 2 application on apache hosting server
Sorry to rock the boat I hope this doesn't attract more questions I'm only going to comment once :) ...
51
angular Support adding rel=canonical link tags using an included service
Eventually there will be some DocumentService part of Core that will handle both Meta/Link elements ...
50
angular Concept of Angular (ngZone + ChangeDetection) better than concept React, Vue (Virtual DOM)?
Concept of Angular (ngZone + ChangeDetection) better than concept React Vue (Virtual DOM)? If you ca...
50
angular I'd like to be able to use ngModel without specifying a name
Thank you all for the great feedback - very helpful! Here's how we are thinking about it: In the cas...
49
angular Model values not trimming automatically in angular 2
@laskoviymishka White space it already something If you are a programmer and think globally - yes ...
46
angular HttpClient - HttpErrorResponse not json but blob
I created this interceptor as a temporary solution until this one is fixed: I'm submitting a.. ...
45
angular Router's ActivatedRoute data returns empty {} if module is lazy
data is available only with this hell-like construction: And this is if you have children: ...
44
angular Misleading errormessage when using HostBinding with @animation trigger but no defined animations
The error message is not fine The error message says you're importing BrowserAnimationsModule incorr...
42
angular Memory leak when FormControlName created/destroyed few times
This issue has been around for nearly 3 years now (I usually don't like to start a message this way ...
41
angular Remove System.import() usage in favor of import()
I use a parser rule in my webpack configuration to disable the warning: https://webpack.js.org/confi...
41
angular Async event subscriber not updating UI after async call
Hi! The issue is that the async call result is outside ngZone thus not triggering the UI update You ...
40
angular Using router.navigate to navigate to another component does not invoke the onInit method
I have the same issue Angular is running in a Cordova app for iOS I tried the router-version 4.1.3 (...
37
angular Router: AoT compilation fails when using a function with loadChildren
Calling functions or calling new is not supported in metadata when using AoT This includes things li...
35
angular Provide a mock service using TestBed
I was having this issue as well however I noticed that my @component metadata still had the provider...
35
angular Angular2 download excel file from Web API, file is corrupt
@healkar01 I had the same issue and I resolved using native angular2 http request in this way: Backe...
35
angular 4.0.0-rc.6 [platform-server] - Cannot find module '@angular/animations/browser'. & other errors
(Just incase others find it) Make sure @angular/animations is installed as a dependency and the erro...
35
angular HttpClient mapping to typescript types not working
I agree with all the previous comments I find the syntax misleading widget.service.zip widget.servic...