You might remember that I had written a tutorial titled, Simple User Login in a Vue.js Web Application, which demonstrated how to navigate between routes and check a variable to determine if a user should in fact be allowed to be on a particular route. This previous tutorial focused on applying logic after the user had already completed the navigation process, rather than during or prior. While this is a good introduction to becoming familiar with the Vue.js router, it isn’t a realistic approach to handling user login and route restrictions.
The recommended approach is to use navigation guards, sometimes referred to as route guards.
In this tutorial, we’re going to see how to use Vuex with Vue.js navigation guards to protect certain pages within the application from unauthorized access.
If you haven’t seen the previous tutorial on user login, don’t worry as the two tutorials are not dependent on each other. However, the other tutorial is still very relevant if you’re looking to expand your skills and get a better understanding of the Vue.js JavaScript framework.
To keep this tutorial simple and easy to understand, we’re going to start with a fresh project generated with the Vue CLI. As of right now, I’m using 3.9.3 of the Vue CLI.
From the Terminal or Command Prompt, execute the following command to create a new project:
vue create navguard-project
While we’re going to be using the Vue Router, don’t choose to enable it with the CLI. Instead choose the default settings when prompted and complete the wizard for project generation.
Once the project is created, navigate into the project and execute the following:
npm install vuex --save
npm install vue-router --save
We’ll be using Vuex to store our authentication state throughout the application. In a realistic scenario, the JSON Web Token (JWT) would be stored, but this is a basic example with no connected APIs or databases. We’re using the Vue Router to navigate between routes or components.
Before we worry about our navigation guards or routing logic, let’s first create the component that we wish to secure. In the project’s src/components directory, create a Secure.vue file with the following code:
<template>
<div>
<h1>Secure Area</h1>
<p>
This is a secure area
</p>
</div>
</template>
<script>
export default {
name: "Secure"
}
</script>
<style scoped></style>
The component isn’t going to get any more complex than what you see above. Remember, the point of this project is to make it so the user can only access this particular component if we allow them to.
The next step is to create the unprotected login page.
While the Secure.vue component will have restrictions, we’re going to create a Login.vue component that will be readily available whenever requested.
Within the project’s src/components directory, create a Login.vue file with the following code:
<template>
<div>
<h1>Login</h1>
<input type="text" name="username" v-model="input.username" placeholder="Username" />
<input type="password" name="password" v-model="input.password" placeholder="Password" />
<button type="button" v-on:click="login()">Login</button>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {
input: {
username: "",
password: ""
}
}
},
methods: {
login() {
if(this.input.username == "admin" && this.input.password == "pass") {
// Additional logic here...
} else {
console.log("The username and / or password is incorrect");
}
}
}
}
</script>
<style scoped></style>
While very basic, the above code will change slightly as we progress. For now, the purpose is to show a simple form with bound variables. When trying to submit the form, the login
method is called and we validate the username and password information that was provided.
I cannot stress this enough, but this is a simple mock data example. In a production scenario, you’ll likely make an HTTP request to your server to validate the username and password that the user provides with the username and password found in your database. This is outside the scope of what we want to accomplish.
With the two components, which will eventually be routes, out of the way, we can focus on our data store. This is not to be confused with a database as this data store is just a global storage mechanism to be used throughout the lifespan of the users session in the application.
To configure the Vuex store, we need to make some changes to the project’s src/main.js file. Open this file and add the following:
import Vue from 'vue'
import Vuex from "vuex"
import App from './App.vue'
Vue.config.productionTip = false;
Vue.use(Vuex);
const store = new Vuex.Store(
{
state: {
authenticated: false
},
mutations: {
setAuthentication(state, status) {
state.authenticated = status;
}
}
}
);
new Vue({
render: h => h(App),
store: store
}).$mount('#app')
Notice that we’ve imported Vuex, defined a new store, and added it to the main Vue
constructor.
In the store itself, we have one variable that we wish to use. The authenticated
variable is a boolean that is defaulted to false. The idea behind this is that when false, we shouldn’t be able to access the secure page, but when true, we should be able to. Because we cannot change data directly in the variable, we have to create a mutation. The purpose of the mutation is to set the variable between true and false.
Now that we have Vuex configured, we can make a small change to the Login.vue component:
methods: {
login() {
if(this.input.username == "admin" && this.input.password == "pass") {
this.$store.commit("setAuthentication", true);
} else {
console.log("The username and / or password is incorrect");
}
}
}
In the login
method, we are now leveraging the mutation through a commit
command. If the login is successful, we say that our authentication status is now true. We’ll be revisiting this login
method again, but at least now we can change the authentication status throughout the application.
This leads us to the actual navigation.
As it stands, we have two components, but they don’t really behave like pages that you can navigate between. This means we have to change a few things to turn them into routes.
Open the project’s src/main.js file and make the following changes:
import Vue from 'vue'
import VueRouter from "vue-router"
import Vuex from "vuex"
import App from './App.vue'
import Login from "./components/Login.vue"
import Secure from "./components/Secure.vue"
Vue.config.productionTip = false;
Vue.use(VueRouter);
Vue.use(Vuex);
const store = new Vuex.Store(
{
state: {
authenticated: false
},
mutations: {
setAuthentication(state, status) {
state.authenticated = status;
}
}
}
);
const router = new VueRouter({
routes: [
{
path: '/',
redirect: {
name: "login"
}
},
{
path: "/login",
name: "login",
component: Login
},
{
path: "/secure",
name: "secure",
component: Secure,
beforeEnter: (to, from, next) => {
if(store.state.authenticated == false) {
next(false);
} else {
next();
}
}
}
]
});
new Vue({
render: h => h(App),
router: router,
store: store
}).$mount('#app')
Notice that this time, we’ve also imported the Vue Router, defined some routes, and added it to the Vue
constructor method.
There are two routes in our router, one for login and one for a secure page. Since the login route is not at the root path, if someone tries to access the root path, we just redirect them to the login route. What matters the most to us for this example is the beforeEnter
method:
beforeEnter: (to, from, next) => {
if(store.state.authenticated == false) {
next(false);
} else {
next();
}
}
Whenever we try to access the Secure.vue component, the beforeEnter
method triggers. We check the Vuex store for the authentication status and if it says we are not authenticated, we stop what we’re doing and discontinue the navigation into the page. Otherwise, we proceed into the route.
So let’s make some final revisions.
Open the project’s src/App.vue file and make it look like the following:
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
components: {}
}
</script>
<style></style>
We’ve removed a lot of stuff, but the most important thing we have now is the <router-view>
tags. This acts as an outlet to our routes. The templates inside each component will render inside these tags.
Now that we have our components being rendered as routes, we don’t actually perform a navigation yet.
Open the project’s src/components/Login.vue file and make the following change to the login
method:
methods: {
login() {
if(this.input.username == "admin" && this.input.password == "pass") {
this.$store.commit("setAuthentication", true);
this.$router.replace({ name: "secure" });
} else {
console.log("The username and / or password is incorrect");
}
}
}
After we set the authentication status, we attempt to route to the secure area. Remember, if you try to route to the secure area before updating the authentication status, the navigation will not happen because the navigation guard will prevent it.
You just saw how to use navigation guards in a Vue.js application to restrict access to particular routes. This is useful for many things, such as user login within your application, but it is also very useful for many other scenarios.
You can think of this tutorial as a second step to my previous tutorial which focused on simple login, but checking the authentication status after the navigation had already happened.
A video version of this tutorial can be seen below.