Don’t know how to do Vue.js authentication using vue-router? We can help you.
Vue.js is a progressive JavaScript framework for building front-end applications.
If we couple Vue.js with vue-router, we can build high-performance applications with complete dynamic routes.
As part of our Server Management Services, we assist our customers with several Vue.js queries.
Today, let us see how to set up Vue.js authentication using vue-router.
Vue.js authentication using vue-router
In this article, we will look at using vue-router to handle authentication and access control for different parts of our Vue.js application.
An efficient tool, Vue-router can efficiently handle authentication in our Vue application.
Step 1 – Installing 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.
Step 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)
Moving ahead, our Support Engineers 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.
Step 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 beforeEach 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:
- Auth, check for a jwt token showing the user is logged in.
- 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
We redirect the user based on what we are checking for.
Our Support Techs suggest, we 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.
Step 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, our Support Techs create 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.
Step 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.
Step 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.
Moving ahead, 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 procedures? We’d be happy to assist]
Conclusion
Here’s is an effective method our Support Techs employ in order to set up Vue.js authentication using vue-router. If we couple Vue.js with vue-router, we can build high-performance applications.
0 Comments