Vue-router: Multiple guards support

Created on 4 Oct 2016  ·  32Comments  ·  Source: vuejs/vue-router

There is any plan to add multiple guards support for Per-Route Guards?

For the cases where have multiple resolves before access to the route.
for example check if user connected, next check if user is active, next check if resource is available... All before go into route.

maybe passing an array of functions to beforeEach and going to the view after the last next.
something like

{ 
    path: '/hello', 
    component: Hello, 
    beforeEach: [function(from, to, next){
      // first check
      // ...
      next() // goes to seccond check
    }, function(from, to, next){
      // seccond check
      // ...
      next() // If last check -> goes to component Hello
    }] 
  }

Would be great

Most helpful comment

If anyone is interested, I just published my implementation of multiple guards:
https://www.npmjs.com/package/vue-router-multiguard

To use it you just have to pass your guards array as an argument to multiguard like this:

beforeEnter: multiguard([guard1, guard2])

All 32 comments

Hi, thanks for filling this issue.
I don't think it's worth complicating the api for this, as you can just check all of them inside one hook.. What you probably need instead in this case, is Promises to chain those checks with then and avoid nested callbacks.

Well I did this function to solve it

function operate (guards, from, to, lastNext, i) {
  let guard = guards[i]
  if (guards.length === i + 1) {
    guard(from, to, lastNext)
  } else {
    guard(from, to, function (nextArg) {
      switch (typeof (nextArg)) {
        case 'undefined':
          operate(guards, from, to, lastNext, i + 1)
          break
        case 'object':
          lastNext(nextArg)
          break
        case 'boolean':
          lastNext(nextArg)
          break
        case 'string':
          lastNext(nextArg)
          break
      }
    })
  }
}

function GuardsCheck (ListOfGuards) {
  return function (from, to, next) {
    operate(ListOfGuards, from, to, next, 0)
  }
}

and the usage is like

const routes = [
  { path: '/', component: App},
  { path: '/login', component: LoginCmp },
  { path: '/signup', component: SignupCmp },
  { path: '/dashboard', component: DashboardCmp, beforeEnter: GuardsCheck([
    function userConnected (from, to, next){
      // some logic...
      next()
    },
    function userActive(from, to, next){
      //another logic...
      next(false)
    },
    function configuredDashboard(from, to, next){
      // never goes here because chain has stopped
      next()
    }
  ])},
]

It's something like middlewares on express, similar to promises chain but It's nested callbacks on a recursive function.

Supporting arrays seems to be the intuitive, clean way to do it (IMHO). In a real world app it is probably more likely there are multiple guards per route with each route having a unique combination of guards.

I just hit exactly this. I have 2 guards which are used by multiple routes, some individually, and some in pairs. I would love to be able to do this:

routes: [{
    path: '/',
    component: load('Chooser'),
    beforeEnter: authenticateIfNeeded
}, {
    path: '/:event',
    component: load('Event'),
    beforeEnter: [authenticateIfNeeded, bindEvent]
}, {
.
.
.

Be great if this could be reconsidered...

this is needed! +1

beforeEnter: () => { authenticateIfNeeded(); bindEvent() }

Is that really so bad? that's like 10 characters more ...

I like the look of that. Unfortunately, it results in:

TypeError: next is not a function

Presumably the definitions of the guards needs to change too? Currently I have this kind of thing:

const bindEvent = (to, from, next) => {
  console.log('--R> bindEvent:', to)
  // Do stuff here
  next()
}

oh, right - i'm stupid. It won'T work like that, ignore it.

If anyone is interested, I just published my implementation of multiple guards:
https://www.npmjs.com/package/vue-router-multiguard

To use it you just have to pass your guards array as an argument to multiguard like this:

beforeEnter: multiguard([guard1, guard2])

@LinusBorg @fnlctrl possible to reopen this, and maybe include parts of @atanas-angelov-dev 's implementation of this in a feature release?

```
beforeEnter: (to, from, next) => {
Guard(to, from, next);
AnotherGuard(to, from, next);
}
````
It's not that bad.

...that won't work though

    beforeEnter: (to, from, next) => { 
        Guard(to, from, next); 
        AnotherGuard(to, from, next);
    }
   // Guard
  export default function(to, from, next) {
     console.log('Guard');
     next();
  }
   // AnotherGuard
  export default function(to, from, next) {
     console.log('AnotherGuard');
     next();
  }

screenshot

@LinusBorg seems okay. What i am missing?

Sure, both will run - but as soon as one of calls next(), the next registered hook will be called, even an though maybe the other may want to cancel before that.

Oh, you're right. I didn't get it.

What is the update on this? Can we get something like the solution @atanas-angelov-dev posted built-in to vue-router?

Any updates on this?

This issue is closed, so: no. No one is workgin on this and no one will work on this, That's why the issue is closed.

Hacky way:

beforeEnter: (to, from, next) => {
    const nextNext = Guard.bind(this, to, from, next);
    AnotherGuard(to, from, nextNext);
}

Pretty way:

function multiple(guards) {
    return (to, from, next) => {
        const stack = [].concat(guards);
        function another() {
            const guard = stack.pop();
            guard ? guard(to, from, another) : next()
        }
        another();
    }
}

and then:

    path '/home',
    beforeEnter: multiple([
        Guard,
        AnotherGuard,
    ]),
    ...

@erikweebly unless I'm mistaken, the guards themselves would not have access to the real router next() and thus will not be able to call it with any argument (as they receive another instead). This means that whatever these guards do the request will always continue instead of being affected by a guard that wants to stop it, for example.

I don't mean to spam this thread but my package solves these issues in a simplistic way:
https://www.npmjs.com/package/vue-router-multiguard

Yeah, I'm using your package with great success, thanks a lot. I think, however, that this feature should be available natively

Here is (yet) another use case for this feature.

We have a number of application level modules we import with NPM. In each of these modules we want to be able to define beforeEach logic for the router and import those modules automatically.

Considering Vuex supports adding modules dynamically, it's a bit weird that Vue Router would not support this kind of functionality.

@posva @fnlctrl Would guys reconsider this issue given the work done by @atanas-angelov-dev. I think his work would be easy to integrate and I don't think this complicates the API much to allow an array instead of just a function seems very intuitive even without explicit documentation. I could make a pull request.

It's actually one of the things I want to revisit but right now I'm focusing on bugs first and triaging existing issues that may be bugs

Great! I'm glad to hear you guys are considering it. When things ease up, I can help get a pull request going if it helps.

@posva I'm also open to reworking my implementation into a PR so feel free to let me know if that works for you when the time comes.

@posva please consider reopening this issue so it doesn't get lost.

use case here is: beforeEnter: [requireAuth, anotherGuard]

Just ran into this too, was surprised putting the function in an array didn't work cause that's the Vue way

I use something like this

// allows using multiple guards for routes
function guards(guards)
{
    return async (to, from, next) => {

        // a flag to discard remaining guards
        let changed = false;

        // handle next for multiple guards
        const mNext = function (value)
        {
            // prevent calling next after it is already resolved with a value
            if(changed) return;

            // if we have 'value' reslove next with the value and set changed flag 
            if(typeof value != 'undefined')
            {
                changed = true;
                next(value);
            }
        };

        // call guards
        for (let i = 0; i < guards.length; i++)
        {
            if(changed) break;
            await guards[i](to, from, mNext);
        }

        // move on if none of guards resolved next with a value
        if(!changed) next();

    }
}

and then

    {
        path: '/:lang/app/board',
        name: 'board',
        component: components.BoardView,
        beforeEnter: guards([guardOne, guardTwo])
    }

async function guardOne(to, from, next) {

    await new Promise(r => {
        setTimeout(() => {
            r();
        }, 5000);
    });

    next();

};

function guardTwo(to, from, next) {
    next(false);
};

This functionality would really help me right now, as many of my routes have different combinations of guards. It shouldn't be the responsibility of everyone consuming Vue to write utility code for executing multiple guards on a route using one guard. I understand that it can be accomplished, it's just a pain when every person who needs it writes it themselves.

I also understand a package exists that enables this type of functionality, but native support is ideal, especially for a feature like this.

I haven't seen an opinion from @yyx990803 and I would really appreciate your input on this matter.

Current workaround: https://www.npmjs.com/package/vue-router-multiguard
if you are interested in this feature, upvote #2688 so I can prioritize feature requests

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Lyfme picture Lyfme  ·  3Comments

yyx990803 picture yyx990803  ·  3Comments

alexstanbury picture alexstanbury  ·  3Comments

marcvdm picture marcvdm  ·  3Comments

saman picture saman  ·  3Comments