By Dotun Jolaoso
Author: Crazy geek
Original text: blog.logrocket.com/vue-middlew…
** is strictly prohibited without permission
Typically, when you build a SPA, you need to secure some routes. For example, if we had a Dashboard route that only authenticated users could access, we could use Auth middleware to ensure that legitimate users could access it.
In this tutorial, we will learn how to implement middleware pipelines for Vue applications using vue-Router.
What is a middleware pipeline?
Middleware pipelines are a bunch of different middleware pieces that run in parallel with each other.
Continuing the previous example, suppose there is another route on /dashboard/movies that we only want subscribers to access. We already know that to access the Dashboard route, you need to authenticate. So how do you secure /dashboard/movies routes to ensure that only authenticated and subscribed users can access them? By using middleware pipelines, you can link multiple middleware together and ensure that they run in parallel.
start
Start by quickly building a new Vue project with the Vue CLI.
vue create vue-middleware-pipeline
Copy the code
Install dependencies
Once the project directory is created and installed, switch to the newly created directory and run the following command from your terminal:
npm i vue-router vuex
Copy the code
Vue-router – Is the official router of vue. js
Vuex – Is the state management library for Vue
Create components
Our program will consist of three components.
Login – This component is presented to users who have not been authenticated.
Dashboard – This component is displayed to users who have logged in.
Movies – We display this component to users who are logged in and have valid subscriptions.
Let’s create these components. Switch to the SRC/Components directory and create the following files: dashboard.vue, login. vue, and movies.vue
Edit the login. vue file with the following code:
<template>
<div>
<p>This is the Login component</p>
</div>
</template>
Copy the code
Edit the dashboard.vue file with the following code:
<template>
<div>
<p>This is the Dashboard component for authenticated users</p>
<router-view/>
</div>
</template>
Copy the code
Finally, add the following code to the movies.vue file:
<template>
<div>
<p>This is the Movies component for authenticated and subscribed users</p>
</div>
</template>
Copy the code
Create the store
As far as Vuex is concerned, store is just a container for holding the state of our program. It allows us to determine whether the user is authenticated and to check whether the user is subscribed.
In the SRC folder, create a store.js file and add the following code to it:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
user: {
loggedIn: false.isSubscribed: false}},getters: {
auth(state) {
return state.user
}
}
})
Copy the code
A store contains a User object in its state. The user object contains the loggedIn and isSubscribed attributes, which help us determine whether the user is loggedIn and has a valid subscription. We also define a getter in the store to return the User object.
Define the routing
Before you create routes, you should define them and associate the corresponding middleware to be attached to them.
Everyone except authenticated users can access /login. When authenticated users access this route, they should be redirected to the Dashboard route. This route should be accompanied by a guest middleware.
Only authenticated users can access/Dashboard. Otherwise, users should be redirected to the /login route when accessing this route. We associate auth middleware with this route.
/dashboard/movies is only accessible to authenticated and subscribed users. The routing is protected by isSubscribed and Auth middleware.
Create routing
Next, create a Router folder in the SRC directory, and then create a router.js file in that folder. Edit the file with the following code:
import Vue from 'vue'
import Router from 'vue-router'
import store from '.. /store'
import Login from '.. /components/Login'
import Dashboard from '.. /components/Dashboard'
import Movies from '.. /components/Movies'
Vue.use(Router)
const router = new Router({
mode: 'history'.base: process.env.BASE_URL,
routes: [{path: '/login'.name: 'login'.component: Login
},
{
path: '/dashboard'.name: 'dashboard'.component: Dashboard,
children: [{
path: '/dashboard/movies'.name: 'dashboard.movies'.component: Movies
}
],
}
]
})
export default router
Copy the code
Here, we create a new Router instance, passing several configuration options and a Routes attribute that accepts all the routes we defined earlier. Note that these routes are currently unprotected. We will solve the problem soon.
Next, the route and store are injected into the Vue instance. Edit the SRC /main.js file with the following code:
import Vue from 'vue'
import App from './App.vue'
import router from './router/router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h= > h(App),
}).$mount('#app')
Copy the code
Creating middleware
Create a middleware folder in the SRC/Router directory, and create guest.js, auth.js, and IsSubscribed. Add the following code to the guest.js file:
export default function guest ({ next, store }){
if(store.getters.auth.loggedIn){
return next({
name: 'dashboard'})}return next()
}
Copy the code
The guest middleware checks whether the user is authenticated. If authenticated, it is redirected to the Dashboard path.
Next, edit the auth.js file with the following code:
export default function auth ({ next, store }){
if(! store.getters.auth.loggedIn){return next({
name: 'login'})}return next()
}
Copy the code
In Auth middleware, we use Store to check if a user is currently authenticated. Depending on whether the user is logged in, we either continue the request or redirect it to the login page.
Use the following code to edit the isSubscribed.js file:
export default function isSubscribed ({ next, store }){
if(! store.getters.auth.isSubscribed){return next({
name: 'dashboard'})}return next()
}
Copy the code
The middleware in isSubscribed is similar to the Auth middleware. We use store to check if the user subscribes. If the user is subscribed, they can access the expected route, otherwise they are redirected back to the Dashboard page.
Protect the routing
Now that you’ve created all the middleware, let’s leverage it to secure the route. Edit the SRC /router/router.js file with the following code:
import Vue from 'vue'
import Router from 'vue-router'
import store from '.. /store'
import Login from '.. /components/Login'
import Dashboard from '.. /components/Dashboard'
import Movies from '.. /components/Movies'
import guest from './middleware/guest'
import auth from './middleware/auth'
import isSubscribed from './middleware/isSubscribed'
Vue.use(Router)
const router = new Router({
mode: 'history'.base: process.env.BASE_URL,
routes: [{
path: '/login'.name: 'login'.component: Login,
meta: {
middleware: [
guest
]
}
},
{
path: '/dashboard'.name: 'dashboard'.component: Dashboard,
meta: {
middleware: [
auth
]
},
children: [{
path: '/dashboard/movies'.name: 'dashboard.movies'.component: Movies,
meta: {
middleware: [
auth,
isSubscribed
]
}
}],
}
]
})
export default router
Copy the code
Here, we import all the middleware and then define a meta-field containing the middleware array for each route. The middleware array contains all the middleware we want to associate with a particular route.
Vue route navigation guard
We use the navigational guard provided by the Vue Router to protect the route. These navigators protect routes by redirecting or canceling them.
One of the guards is the global guard, which is usually the hook that is called before the route is triggered. To register a global vanguard, define a beforeEach method on the Router instance.
const router = new Router({ ... })
router.beforeEach((to, from, next) = > {
//necessary logic to resolve the hook
})
Copy the code
The beforeEach method receives three arguments:
To: This is the route we intend to access.
From: This is our current route.
Next: This is the function that calls the hook.
Running middleware
Use the beforeEach hook to run our middleware.
const router = newRouter({ ... }) router.beforeEach((to, from, next) = > {
if(! to.meta.middleware) {return next()
}
const middleware = to.meta.middleware
const context = {
to,
from,
next,
store
}
return middleware[0] ({... context }) })Copy the code
We first check that the route we are currently processing has a meta-field containing the Middleware property. If the Middleware property is found, it is assigned to a const variable. Next we define a Context object that contains everything we need to pass to each middleware. The first middleware in the middleware array is then called as a function, passing in the context object.
Try accessing the/Dashboard route and you should be redirected to the Login route. This is because the/SRC/store. Store in js. State. The user. The loggedIn attribute is set to false. Will store. State. The user. The loggedIn attribute to true, you should be able to access/dashboard routing.
Now the middleware is running, but it’s not the way we want it. Our goal is to implement a pipeline that can run multiple middleware for a specific path.
returnMiddleware [0] ({... context})Copy the code
Notice that in this line of code in the block above, we only call the first middleware passed from the middleware array in the META field. So how do we ensure that other middleware (if any) contained in the array are also called? This is where pipes come in handy.
Create the pipe
Switch to the SRC/Router directory and create a middlewarePipeline.js file. Add the following code to the file:
function middlewarePipeline (context, middleware, index) {
const nextMiddleware = middleware[index]
if(! nextMiddleware){return context.next
}
return (a)= > {
const nextPipeline = middlewarePipeline(
context, middleware, index + 1) nextMiddleware({ ... context,next: nextPipeline })
}
}
export default middlewarePipeline
Copy the code
MiddlewarePipeline takes three parameters:
Context: This is the context object we created earlier, which can be passed to each middleware in the stack.
Middleware: This is the Middleware array itself defined on the Meta field of the route.
Index: This is the index of the current middleware running in the Middleware array.
const nextMiddleware = middleware[index]
if(! nextMiddleware){return context.next
}
Copy the code
In this case, we simply pull out the middleware in the index passed to the middlewarePipeline function. If middleware is not found in index, the default next callback is returned.
return (a)= > {
const nextPipeline = middlewarePipeline(
context, middleware, index + 1) nextMiddleware({ ... context,next: nextPipeline })
}
Copy the code
We call nextMiddleware to pass the context, and then nextPipeline const. It is worth noting that the middlewarePipeline function is a recursive function that calls itself to get the next middleware to run on the stack, incrementing the index to 1.
Put them together
Let’s use middlewarePipeline. Edit the SRC /router/router.js file like this:
import Vue from 'vue'
import Router from 'vue-router'
import store from '.. /store'
import Login from '.. /components/Login'
import Dashboard from '.. /components/Dashboard'
import Movies from '.. /components/Movies'
import guest from './middleware/guest'
import auth from './middleware/auth'
import isSubscribed from './middleware/isSubscribed'
import middlewarePipeline from './middlewarePipeline'
Vue.use(Router)
const router = new Router({
mode: 'history'.base: process.env.BASE_URL,
routes: [{
path: '/login'.name: 'login'.component: Login,
meta: {
middleware: [
guest
]
}
},
{
path: '/dashboard'.name: 'dashboard'.component: Dashboard,
meta: {
middleware: [
auth
]
},
children: [{
path: '/dashboard/movies'.name: 'dashboard.movies'.component: Movies,
meta: {
middleware: [
auth,
isSubscribed
]
}
}],
}
]
})
router.beforeEach((to, from, next) = > {
if(! to.meta.middleware) {return next()
}
const middleware = to.meta.middleware
const context = {
to,
from,
next,
store
}
return middleware[0] ({... context,next: middlewarePipeline(context, middleware, 1)})})export default router
Copy the code
Here, we use the middlewarePipeline
to run subsequent middleware contained in the stack.
return middleware[0] ({... context,next: middlewarePipeline(context, middleware, 1)})Copy the code
After calling the first middleware, the use of the middlewarePipeline function also calls subsequent middleware contained in the stack until no more middleware is available.
If you access the /dashboard/movies route, you should be redirected to /dashboard. This is because users are currently authenticated but have no valid subscriptions. If the store in the store. The state. The user. The isSubscribed attribute set to true, it should be able to access/dashboard/movies routing.
conclusion
Middleware is a good way to protect different routes in an application. This is a very simple implementation that uses multiple middleware to secure a single route in a Vue application. You can find it at github.com/Dotunj/vue-…