DEV Community

loading...

Discussion on: Creating an Authentication Navigation Guard in Vue

Collapse
wparad profile image
Warren Parad

While this definitely works, it creates a bit of a monolith piece of code where your router.beforeEach hook now needs to know about the guards for each route. Rather than doing that I would suggest instead do use the functionality of the router.routes property which contains a beforeEnter property for each route. Instead of populating a metadata.requiresAuth, have this instead:

{
  path: '/team-health',
  name: 'TeamHealth',
  beforeEnter: multiguard([authManager.requireAuth(), teamManager.requireTeamAccess()]),
  component: TeamHealth
}

We're doing a bunch of things here:

  • Using vue-router-multiguard, which expects a list of async callback functions using next
  • custom guards for each route

The Team Health route (for Teaminator) both requires the user to be logged in, but also that they have a valid team. It's possible that they got removed or haven't selected a team yet, in which case we'll redirect them somewhere else.

But this also allows other routes to look like this:

{
  path: '/support',
  name: 'Support',
  component: Support,
  beforeEnter: authManager.attemptAuth()
}

On our support route, we'll attempt the login, but not require it. Since it is a great UX to let the user report problems for their account, we attempt to logi the user in, but it is also important to let them report problems without needing to login in case our authorization service is down for some reason.

Just in case you were curious, our requireAuth function looks like this:

requireAuth() {
  return async (to, from, next) => {
    const options = { from: from.fullPath, to: to.fullPath, route: to.fullPath };
    try {
      const result = await this.authProvider.getClient().ensureLoggedIn(options);
      if (!result || !result.success) {
        next(false);
        return;
      }

      // Change the global location
      if (result.path) {
        next(false);
        window.location.replace(result.path);
        return;
      }

      this.store.commit('setinvalidTokenCalls', 0);

      // Otherwise only change the local location
      if (result.route) {
        next({ path: result.route });
        return;
      }

      // If there is no redirect set, just continue
      next();
    } catch (error) {
      this.logger.error({ title: 'User login error', exception: error });
      this.alertHandler.createAlert(
        'Login issue',
        `We are having trouble logging you in.
         If this continues please contact support@teaminator.io`,
         null, 'danger', 3000);
      next(false);
    }
  };
}
Collapse
laurieontech profile image
Laurie Author

Definitely another way to do it.

So I had a chance to review this more closely, and it's a wonderful solution! But the constraints are slightly different than the solution I presented.

I point this out because I think that's the most important piece. Understand what your use case is, and determine the solution that makes sense. Yours is going to help a lot of people! As, I hope, will mine. And it's necessary that they look closely at their requirements when selecting their solution of choice.