Vue.js authentication using vue-router - How to implement it ?









Vue.js is a progressive JavaScript framework for building front-end applications. In conjuction with vue-router, we can build high performance applications with complete dynamic routes. 

Basically, Vue-router is an efficient tool and can efficiently handle authentication in our Vue application.

Here at Ibmi Media, as part of our Server Management Services, we regularly help our Customers to perform Vue.js related queries.

In this context, we shall look into steps to set up Vue.js authentication using vue-router.


More about Vue.js ?

Vue.js is a JavaScript framework that allows you to easily render dynamic data to the DOM, bind data to DOM elements, and manage/maintain your app's state (local storage) without requiring a user to reload the browser.


How to implement Vue.js authentication using vue-router ?

Here, you will learn how vue-router can handle authentication and access control for different parts of our Vue.js application.

Vue-router is an efficient tool which can efficiently handle authentication in our Vue application.


1. Install Vue CLI and Creating an Application

Initially, we will install the Vue CLI and create a Vue application with it:

$ npm install -g @vue/cli
$ npm install -g @vue/cli-init
$ vue init webpack vue-router-auth

We go ahead and follow the setup prompt and complete the installation. In case we aren’t sure of an option, click the return key (ENTER key) for the default option. 

When asked to install vue-router, we accept the option.


2. Set Up Node.js Server

Next, for our Node.js server, we will use SQLite as the database.

We install SQLite driver:

$ npm install –save sqlite3

We will use bcrypt to hash all our passwords:

$ npm install –save bcrypt

To confirm the users we authenticate when they make a request to a secured part of our application we will use JWT.

In order to install the JWT package, run:

$ npm install jsonwebtoken –save

To read json data, we need a body-parser:

$ npm install –save body-parser

Now, let us create a Node.js server to handle user authentication.


Initially, we create a new directory named server. Here we will store everything we use to make our node backend.

In the server directory, create a file and save it as app.js. Add the following to it:

“use strict”;
const express = require(‘express’);
const DB = require(‘./db’);
const config = require(‘./config’);
const bcrypt = require(‘bcrypt’);
const jwt = require(‘jsonwebtoken’);
const bodyParser = require(‘body-parser’);
const db = new DB(“sqlitedb”)
const app = express();
const router = express.Router();
router.use(bodyParser.urlencoded({ extended: false }));
router.use(bodyParser.json());
In addition, we will define CORS middleware to ensure not to run into any cross-origin resource errors:
// CORS middleware
const allowCrossDomain = function(req, res, next) {
res.header(‘Access-Control-Allow-Origin’, ‘*’);
res.header(‘Access-Control-Allow-Methods’, ‘*’);
res.header(‘Access-Control-Allow-Headers’, ‘*’);
next();
}
app.use(allowCrossDomain)


Next, you need to define the route for registering a new user:

router.post(‘/register’, function(req, res) {
db.insert([
req.body.name,
req.body.email,
bcrypt.hashSync(req.body.password, 8)
],
function (err) {
if (err) return res.status(500).send(“There was a problem registering the user.”)
db.selectByEmail(req.body.email, (err,user) => {
if (err) return res.status(500).send(“There was a problem getting user”)
let token = jwt.sign({ id: user.id }, config.secret, {expiresIn: 86400 // expires in 24 hours
});
res.status(200).send({ auth: true, token: token, user: user });
});
});
});

Here, first, we pass the request body to a database method and pass a callback function to handle the response from the database operation. We define error checks to ensure we provide accurate information to users.

When a user is successfully registered, we select the user data by email and create an authentication token for the user with the jwt package.

We use a secret key in our config file to sign the auth credentials. 

Hence, we can verify a token sent to our server and a user cannot fake an identity.

Then, we define the route for registering an administrator and logging in:

router.post(‘/register-admin’, function(req, res) {
db.insertAdmin([
req.body.name,
req.body.email,
bcrypt.hashSync(req.body.password, 8),
1
],
function (err) {
if (err) return res.status(500).send(“There was a problem registering the user.”)
db.selectByEmail(req.body.email, (err,user) => {
if (err) return res.status(500).send(“There was a problem getting user”)
let token = jwt.sign({ id: user.id }, config.secret, { expiresIn: 86400 // expires in 24 hours
});
res.status(200).send({ auth: true, token: token, user: user });
});
});
});
router.post(‘/login’, (req, res) => {
db.selectByEmail(req.body.email, (err, user) => {
if (err) return res.status(500).send(‘Error on the server.’);
if (!user) return res.status(404).send(‘No user found.’);
let passwordIsValid = bcrypt.compareSync(req.body.password, user.user_pass);
if (!passwordIsValid) return res.status(401).send({ auth: false, token: null });
let token = jwt.sign({ id: user.id }, config.secret, { expiresIn: 86400 // expires in 24 hours
});
res.status(200).send({ auth: true, token: token, user: user });
});
})

For login, we use bcrypt to compare our hashed password with the user-supplied password. If they are the same, we log the user in.

In addition, let us use the Express server to make our application accessible:

app.use(router)
let port = process.env.PORT || 3000;
let server = app.listen(port, function() {
console.log(‘Express server listening on port ‘ + port)
});

We created a server on port: 3000 or any dynamically generated port by our system.

Then, create another file config.js in the same directory and add the following to it:

module.exports = {
‘secret’: ‘supersecret’
};

Finally, create another file db.js, and add the following to it:

“use strict”;
const sqlite3 = require(‘sqlite3’).verbose();
class Db {
constructor(file) {
this.db = new sqlite3.Database(file);
this.createTable()
}
createTable() {
const sql = `
CREATE TABLE IF NOT EXISTS user (
id integer PRIMARY KEY,
name text,
email text UNIQUE,
user_pass text,
is_admin integer)`
return this.db.run(sql);
}
selectByEmail(email, callback) {
return this.db.get(
`SELECT * FROM user WHERE email = ?`,
[email],function(err,row){
callback(err,row)
})
}
insertAdmin(user, callback) {
return this.db.run(
‘INSERT INTO user (name,email,user_pass,is_admin) VALUES (?,?,?,?)’,
user, (err) => {
callback(err)
})
}
selectAll(callback) {
return this.db.all(`SELECT * FROM user`, function(err,rows){
callback(err,rows)
})
}
insert(user, callback) {
return this.db.run(
‘INSERT INTO user (name,email,user_pass) VALUES (?,?,?)’,
user, (err) => {
callback(err)
})
}
}
module.exports = Db

Now we have a class for our database to abstract the basic functions we need.


3. Updating the vue-router File

The vue-router file is in the ./src/router/ directory. In the index.js file, we will define all the routes we want our application to have. 

This is different from what we did with our server.

Initially, open the file and add the following:

import Vue from ‘vue’
import Router from ‘vue-router’
import HelloWorld from ‘@/components/HelloWorld’
import Login from ‘@/components/Login’
import Register from ‘@/components/Register’
import UserBoard from ‘@/components/UserBoard’
import Admin from ‘@/components/Admin’
Vue.use(Router)

Then, let us define the routes for our application:

let router = new Router({
mode: ‘history’,
routes: [
{
path: ‘/’,
name: ‘HelloWorld’,
component: HelloWorld
},
{
path: ‘/login’,
name: ‘login’,
component: Login,
meta: {
guest: true
}
},
{
path: ‘/register’,
name: ‘register’,
component: Register,
meta: {
guest: true
}
},
{
path: ‘/dashboard’,
name: ‘userboard’,
component: UserBoard,
meta: {
requiresAuth: true
}
},
{
path: ‘/admin’,
name: ‘admin’,
component: Admin,
meta: {
requiresAuth: true,
is_admin : true
}
},
]
})

Here, we can specify additional behavior. 

In our case above, we define some routes as guests, some to require authentication, and only accessible to admin users.

Now, let us handle requests to these routes based on the meta specification:

router.beforeEach((to, from, next) => {
if(to.matched.some(record => record.meta.requiresAuth)) {
if (localStorage.getItem(‘jwt’) == null) {
next({
path: ‘/login’,
params: { nextUrl: to.fullPath }
})
} else {
let user = JSON.parse(localStorage.getItem(‘user’))
if(to.matched.some(record => record.meta.is_admin)) {
if(user.is_admin == 1){
next()
}
else{
next({ name: ‘userboard’})
}
}else {
next()
}
}
} else if(to.matched.some(record => record.meta.guest)) {
if(localStorage.getItem(‘jwt’) == null){
next()
}
else{
next({ name: ‘userboard’})
}
}else {
next()
}
})
export default router

Vue-router has a before Each method. 

It takes three parameters — to, from, and next.

to is where the user wishes to go, from is where the user is coming from, and next is a callback function that continues the processing of the user request.


If the route requires:

i. Auth, check for a jwt token showing the user is logged in.

ii. Auth and is only for admin users, check for auth and check if the user is an admin

a guest, check if the user is logged in

iii. We redirect the user based on what we are checking for.


You need to have next() called at the end of every condition. 

It will prevent the application from failing in the event that there is a condition we forgot to check.


4. Defining Some Components

To test out what we have built, we define a few components. In the ./src/components/ directory, open the HelloWorld.vue file and add the following:

<template>
<div class=”hello”>
<h1>This is homepage</h1>
<h2>{{msg}}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: ‘Hello World!’
}
}
}
</script>
<!– Add “scoped” attribute to limit CSS to this component only –>
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

Then create a new file Login.vue in the same directory and add the following:

<template>
<div>
<h4>Login</h4>
<form>
<label for=”email” >E-Mail Address</label>
<div>
<input id=”email” type=”email” v-model=”email” required autofocus>
</div>
<div>
<label for=”password” >Password</label>
<div>
<input id=”password” type=”password” v-model=”password” required>
</div>
</div>
<div>
<button type=”submit” @click=”handleSubmit”>
Login
</button>
</div>
</form>
</div>
</template>

That is for the HTML template. Now, let us define the script handling login:

<script>
export default {
data(){
return {
email : “”,
password : “”
}
},
methods : {
handleSubmit(e){
e.preventDefault()
if (this.password.length > 0) {
this.$http.post(‘http://localhost:3000/login’, {
email: this.email,
password: this.password
})
.then(response => {
})
.catch(function (error) {
console.error(error.response);
});
}
}
}
}
</script>

Now, we have the email and password data attributes bound to the form fields to collect user input. 

We make a request to the server to authenticate the credentials the user supplies.

Use the response from the server:

[…]
methods : {
handleSubmit(e){
[…]
.then(response => {
let is_admin = response.data.user.is_admin
localStorage.setItem(‘user’,JSON.stringify(response.data.user))
localStorage.setItem(‘jwt’,response.data.token)
if (localStorage.getItem(‘jwt’) != null){
this.$emit(‘loggedIn’)
if(this.$route.params.nextUrl != null){
this.$router.push(this.$route.params.nextUrl)
}
else {
if(is_admin== 1){
this.$router.push(‘admin’)
}
else {
this.$router.push(‘dashboard’)
}
}
}
})
[…]
}
}
}
}

We store the jwt token and user information in localStorage.

Next, we create a Register.vue file and add the following to it:

<template>
<div>
<h4>Register</h4>
<form>
<label for=”name”>Name</label>
<div>
<input id=”name” type=”text” v-model=”name” required autofocus>
</div>
<label for=”email” >E-Mail Address</label>
<div>
<input id=”email” type=”email” v-model=”email” required>
</div>
<label for=”password”>Password</label>
<div>
<input id=”password” type=”password” v-model=”password” required>
</div>
<label for=”password-confirm”>Confirm Password</label>
<div>
<input id=”password-confirm” type=”password” v-model=”password_confirmation” required>
</div>
<label for=”password-confirm”>Is this an administrator account?</label>
<div>
<select v-model=”is_admin”>
<option value=1>Yes</option>
<option value=0>No</option>
</select>
</div>
<div>
<button type=”submit” @click=”handleSubmit”>
Register
</button>
</div>
</form>
</div>
</template>

Then, define the script handling registration:

<script>
export default {
props : [“nextUrl”],
data(){
return {
name : “”,
email : “”,
password : “”,
password_confirmation : “”,
is_admin : null
}
},
methods : {
handleSubmit(e) {
e.preventDefault()
if (this.password === this.password_confirmation && this.password.length > 0)
{
let url = “http://localhost:3000/register”
if(this.is_admin != null || this.is_admin == 1) url = “http://localhost:3000/register-admin”
this.$http.post(url, {
name: this.name,
email: this.email,
password: this.password,
is_admin: this.is_admin
})
.then(response => {
localStorage.setItem(‘user’,JSON.stringify(response.data.user))
localStorage.setItem(‘jwt’,response.data.token)
if (localStorage.getItem(‘jwt’) != null){
this.$emit(‘loggedIn’)
if(this.$route.params.nextUrl != null){
this.$router.push(this.$route.params.nextUrl)
}
else{
this.$router.push(‘/’)
}
}
})
.catch(error => {
console.error(error);
});
} else {
this.password = “”
this.passwordConfirm = “”
return alert(“Passwords do not match”)
}
}
}
}
</script>

This creates the register component and accompanying method to handle user submission of the registration form.

Now, we create the file Admin.vue and add the following:

<template>
<div class=”hello”>
<h1>Welcome to administrator page</h1>
<h2>{{msg}}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: ‘The superheros’
}
}
}
</script>
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

This is the component we will mount when a user visits the admin page.


Finally, we created the file UserBoard.vue and add the following:

<template>
<div class=”hello”>
<h1>Welcome to regular users page</h1>
<h2>{{msg}}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: ‘The commoners’
}
}
}
</script>
<!– Add “scoped” attribute to limit CSS to this component only –>
<style scoped>
h1, h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

This file is visible when a user visits the dashboard page.


5. Setting Up Axios Globally

For all our server requests, we use Axios.

To install Axios, run:

$ npm install –save axios

To make it accessible across all our components, we open the ./src/main.js file and add the following:

import Vue from ‘vue’
import App from ‘./App’
import router from ‘./router’
import Axios from ‘axios’
Vue.prototype.$http = Axios;
Vue.config.productionTip = false
new Vue({
el: ‘#app’,
router,
components: { App },
template: ‘<App/>’
})

Hence we can use Axios in all our components like this.$http.


6. Run the Application

Finally, we need to build all our assets and run them. 

Since we have both the Node.js server and the Vue application, we need them for our application to work.

Next, we open the package.json file and add the below script to run the Node server:

[…]
“scripts”: {
“dev”: “webpack-dev-server –inline –progress –config build/webpack.dev.conf.js”,
“start”: “npm run dev”,
“server”: “node server/app”,
“build”: “node build/build.js”
},
[…]

Eventually, run the following command to start the server:

$ npm run server

Meanwhile, create another terminal instance and run the Vue app:

$ npm run dev

This builds all the assets and starts the application.


[Stuck with the Vue js procedures? We'd be happy to assist. ]

Conclusion

This article will guide you on how to set up Vue.js authentication using vue-router. If we couple Vue.js with vue-router, we can build high-performance applications.

To run a Vue application:

1. Open the package.json file and add the following:

[...]
"scripts": {
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "server": "node server/app",
    "build": "node build/build.js"
  },
[...]

2. We added the server script to help us start up the node server. Now, run the following command to start the server:

npm run server

3. Then create another terminal instance and run the Vue app like this:

npm run dev

This will build all the assets and start the application. You can open the link it shows you to see the application.




Keep In Touch

We hope to hear from you.

Accept File Type: jpg,jpeg,png,txt,pdf,doc,docx