Question regarding guards and redirection

This is probably a frequented question or topic of discussion. I feel like I have researched quite a bit but do not feel like I have received a solid answer.

I would like to set up a simple guard that will redirect to a page if the conditions are not met. Using ionViewCanEnter appears inadequate, and navigation from this function seems futile.

The scenario

The scenario seems simple enough: A page requires a user to be authenticated. If navigation is attempted to that page and they are not authenticated, simply redirect them to a login page (that will ultimately redirect them to their desired destination upon successful authentication).

What I have tried

I tried using ionViewCanEnter to handle the situation. Attempted to redirect using NavController did not work (nothing happened, no logs or errors thrown). I then read that the previous view should handle a failed navigation, however this is not possible if the destination page is navigated through using a deep link from outside the app. In that case there is no previous view to handle the failed load.

What I think should be the solution

It seems like the most logical solution would be to have vanilla angular-esque route guards: a service that would allow you to simply redirect instead of causing all of this confusion. Am I wrong?

Is there a better solution out there that I have just blatantly missed?

My app requires an authentication. To guard all the pages except the login page, I’ve implemented the angular interface HttpInterceptor concept. Doing so, each http requests to my server are handled, as soon one request is done without being authenticated, the user is redirect to the login page.

https://angular.io/guide/http#intercepting-all-requests-or-responses

Don’t know if this apply to your business case, hope it helps.

P.S.: Furthermore the linked documentation, there are many tutorials, you could google “angular HttpInterceptor”

The problem is that I do not want to present the user with the protected page beforehand. Doing so would result in a poor user experience in my use case.

In my case it works fine because all of the pages except the login are guarded. Furthermore on each start of the app, behind the splash screen, I check if logged/not logged in, so my users don’t have the effect you describe.

But like I said, just an idea, don’t know if it’s apply to your business case :wink:

I’m pretty sure that isn’t available in current Ionic routing, and you need to evaluate each nav decision manually (if logged in go here, else go there).

I’ve been also strugling to find an easy solution to this.

First aproach, would be having all private pages extending from an auth class that contains the ionViewCanEnter function with some sort of checking and /or redirection, for instance something like this:

export class AuthBaseClass {
  constructor(public navCtrl: NavController) {

  }

  ionViewCanEnter() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {

        // resolve to which page you go (will be probably login)
        this.navCtrl.setRoot(HomePage);
        this.navCtrl.push(LoginPage);

        // resolve true will go to the desired page
        // resolve false will bo to the login page (or to a page to which you can go)
        resolve(true);
      });
    })
  }
}

The problem with this aproach is that a page is not an authbase class, solving the problem that way does not allow me to extend from any other class, like for instance would make sense to extend from BaseFormPage in a UserCreationFormPage.

Second approach is to import the authbaseclass and repeat in every page the method ionViewCanEnter with a call to a method contained in a AuthBaseClass, I dont really like this solution, because it breaks the Dont Repeat Yourself rule, but I cannot find any other solution.

Any suggestion will be appreciated

Regarding my above comment and HttpInterceptor, I have just recently described how to catch the error and redirect to a page. If that answer could also help here :

The solution I ended up using was to create an Angular-like routing service. In the service I define my “rules” for navigation. Each rule requires a canActivate function that returns a boolean or Observable<boolean>. Each rule also requires a redirect page in case of a false condition. Then I include both my router service and the NavController service in a page. When I am ready to navigate to a new page, I use my service and call the navigate function that I created and pass in the NavController. The service uses the passed in nav controller to either navigate to the destination page or the redirect page, depending on the results of the canActivate function.

So far this has worked really well for us. The code is nowhere near perfect but if you need any code snippets let me know and I can share some with you.

@reedrichards and @jdeanwaite both solutions seems to fit my needs. It’s friday here in spain and I’ve just arrived home, so I’ll take some time during the weekend to study deeply the @reedrichards solution.

I’d also like to have a snippet from the @jdeanwaite solution to be able to compare, could you please post an example of the router?

Maybe we could create a component to help others to solve this issue in the future

1 Like

I’ve done this. I changed my strategy when I realized that Typescript translated const string enums to the actual strings. Now my nav service looks like

export const string enum LandingPageOptions {
   LOGIN_PAGE = 'LoginPage',
   HOME_PAGE = 'HomePage'
}

which allows things like

this.navCtrl.push(LandingPageOptions.HOME_PAGE);

and

this.navCtrl.setRoot(this.navService.landingPage());

where landingPage() returns a const string enum.

I changed because I thought the Typescript structure was pretty, not for a deeper reason. Maybe there’s an advantage in keeping all the Navs in a page, instead of importing a Nav into a provider. Maybe some weird error if you don’t know which Nav you’re looking at? But maybe not. I do think using enums locks out some runtime errors that could arise due to typos, but you could use enums and an imported Nav inside a provider. Not sure what the best practice is here yet.

1 Like

I think we might be mixing a couple of concepts here. The reason that I pass in the NavController instance from a Page into my service is so that I can get the current NavController instance that my service will be navigating upon. I do use the enums as a parameter to the navigate function so that I do not need to type them in specifically.

I will be posting some code snippets soon.

Here is a gist containing our logic. Obviously our rules are hardcoded in there (rules not shown in this Gist) but you should be able to get the idea.


Using this on a page would look like this.

constructor(public navCtrl: NavController, public piypNavCtrl: PiypNavControllerProvider) {}
...

navigate() {
    this.piypNavCtrl.push(MY_PAGE, {id: "123"}, this.navCtrl)
}

The reason this solution works so well for us is that being able to redirect to a Sign In page is much cleaner than navigating to a page and then popping up a sign in modal over the page. The Sign In page receives the original destination and params, so we do not skip a beat. The transition is smoother.

Where this needs work is in Deep linking. If we try to access the protected page directly from outside the app, this service won’t help us. We still don’t have a solution for that yet, but we probably won’t be dealing with that one for a while.

3 Likes