This tutorial is out of date and no longer maintained.
Warning: For educational purposes, tutorial has been greatly simplified to use JSON Web Tokens and localStorage
. This code should not be used in a production environment as it is possible for users to modify the is_admin
value.
Vue.js is a progressive JavaScript framework for building front-end applications. Coupled with vue-router
, you can build high-performance applications with complete dynamic routes. Vue Router is an efficient tool and can efficiently handle authentication in your Vue application.
In this tutorial, you will look at using Vue Router to handle authentication and access control for different parts of your Vue.js application.
To complete this tutorial, you will need:
This tutorial was originally written at the time of Node v10. This tutorial was verified with Node v13.13.0, npm
v6.14.5, vue
v2.5.2, and vue-router
v.3.0.1.
To begin, use the Vue CLI and create a Vue application with it:
- npx -p @vue-cli -p @vue/cli-init vue init webpack vue-router-auth
Follow the setup prompts and complete the installation of this application. If you are not sure of an option, click the return key (ENTER
key) to continue with the default option.
When asked to install vue-router
, accept the option, because you need vue-router
for this application.
Navigate to your project directory:
- cd vue-router-auth
At this point, you have a new Vue project.
Next, you will set up a Node.js server that will handle authentication.
For your Node.js server, you will use SQLite as the database of choice.
Run the following command to install SQLite driver:
- npm install --save sqlite3 # @4.0.1
Note: When running install, you may encounter issues with sqlite3
depending on the version of Node you are running. Refer to the changelog to determine compatibility with your environment.
Because you are dealing with passwords, you will need a way to hash passwords. You will use bcrypt to hash all your passwords. Run the following command to install it:
- npm install --save bcrypt # @2.0.1
Note: When running install, you may encounter issues with bcrypt
depending on the version of Node you are running. Refer to the README to determine compatibility with your environment.
You will also want a way to confirm the users you authenticate when they try to make a request to a secured part of your application. For this, you will use JWT. Run the following command to install the JWT package you will use:
- npm install --save jsonwebtoken # @8.3.0
To read json
data, you will send to your server, you need body-parser
. Run the following command to install it:
npm install --save body-parser # @1.18.3
Now that it’s all set, let’s create a Node.js server that will handle user authentication.
Create a new directory named server
. This is where you will store everything you will use to make your Node backend.
- mkdir server
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());
You have required all the packages you need for your application, defined the database, and created an Express server and router.
Now, let’s define CORS middleware to ensure you do not 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)
Typically, you would use a CORS package here, but you do not have any complicated configurations, so this is fine for the purposes of the tutorial.
Let’s define the route for registering a new user:
...
// Register
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
});
});
});
});
First, you pass the request body [req.body.name
, req.body.email
, req.body.password
] to a database method (which you will define later), and pass a callback function to handle the response from the database operation. As expected, you have defined error checks to ensure we provide accurate information to users.
When a user is successfully registered, you select the user data by email and create an authentication token for the user with the jwt
package you imported earlier. You use a secret key in your config file (which you will create later) to sign the auth credentials. This way, you can verify a token sent to your server and a user cannot fake an identity.
Now, define the route for registering an administrator and logging in, which are similar to register:
...
// Register Admin
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
});
});
});
});
// Login
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, you will use bcrypt
to compare your hashed password with the user-supplied password. If they are the same, you log the user in. If not, feel free to respond to the user how you please.
Now, let’s use the Express server to make your application accessible:
...
// Express
app.use(router)
let port = process.env.PORT || 3000;
let server = app.listen(port, function() {
console.log('Express server listening on port ' + port)
});
You have created a server on port: 3000
or any dynamically generated port by your system.
Then, create another file config.js
in the same directory and add the following to it:
module.exports = {
'secret': 'your_secret'
};
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
You have created a class for your database to abstract the basic functions you will need. You may want to use more generic and reusable methods here for database operations and likely use a promise to make it more efficient. This will allow you to have a repository you can use with all other classes you define (especially if your application uses MVC architecture and has controllers).
The vue-router
file can be found in the ./src/router/
directory.
In the index.js
file, you will define all the routes you want your application to have. This is different from what you did with your server.
Open the file and add the following:
import Vue from 'vue'
import Router from 'vue-router'
// Add Components
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)
You have imported all the components your application will use. You will create the components later.
Now, let’s define the routes for your application:
...
// Routes
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
}
},
]
})
Vue Router allows you to define a meta
on your routes so you can specify additional behavior.
In your case, you have defined:
guest
: which means only users not authenticated will see itrequiresAuth
]: which means only authenticated users will see itis_admin
]Now, let’s handle requests to these routes based on the meta specification:
...
// Meta Handling
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 that is called before each route is processed. This is where you can define our checking condition and restrict user access.
The method 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. Your check is on the to
object.
You will check:
requiresAuth
, check for a jwt
token showing the user is logged in.requiresAuth
and is only for admin users, check for auth and check if the user is an adminguest
, check if the user is logged inYou will redirect the user based on what we are checking for. You will use the name
of the route to redirect, so check to be sure you are using this for your application.
Warning: Always ensure you have next()
called at the end of every condition you are checking. This is to prevent your application from failing in the event that there is a condition you forgot to check.
To test out what you have built, let’s define HelloWorld
, Login
, Admin
, Register
, and UserBoard
. In the ./src/components/
directory, open the HelloWorld.vue
file and add the following:
<template>
<div class="hello">
<h1>This is the homepage</h1>
<h2>{{msg}}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'Hello World!'
}
}
}
</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>
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’s 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>
At this point, you have the email
and password
data attributes bound to the <form>
fields to collect user input. You will make a request to the server to authenticate the credentials the user supplies.
Now, let’s 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')
}
}
}
})
...
}
}
}
...
You will store the jwt
token and user
information in localStorage
so you can access it from all parts of your application. You will redirect the user to whichever part of your application they tried to access before being redirected to /login
. If they came to the /login
directory, you will redirect them based on the user type.
Next, 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>
Now, 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")
}
}
}
}
This is similar in structure to the Login.vue
file. It creates the register component and accompanying method to handle user submission of the registration form.
Now, create the file Admin.vue
and add the following:
<template>
<div class="hello">
<h1>Welcome to the administrator page</h1>
<h2>{{msg}}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'Authenticated Administrators'
}
}
}
</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 you will mount when a user visits the /admin
page.
Finally, create the file UserBoard.vue
and add the following:
<template>
<div class="hello">
<h1>Welcome to the users page</h1>
<h2>{{msg}}</h2>
</div>
</template>
<script>
export default {
data () {
return {
msg: 'Authenticated Users'
}
}
}
</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 file we will see when a user visits the dashboard page.
For all your server requests, you will use axios. axios
is a promise-based HTTP client for the browser and Node.js.
Run the following command to install axios
:
- npm install --save axios # @0.18.0
To make it accessible across all your components, open the ./src/main.js
file and add the following:
import Vue from 'vue'
import App from './App'
import router from './router'
// Add Axios
import Axios from 'axios'
// Add Axios
Vue.prototype.$http = Axios;
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
By defining Vue.prototype.$http = Axios
you have modified the Vue engine and added axios
. You can now use axios
in all your components like this.$http
.
Now that you are done with the application, you need to build all your assets and run it.
Because you have a Node.js server along with a Vue application, you will need both of them for your application to work.
Let’s add a server
script that will help us run your Node server. 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"
},
[...]
You added the server
script to help start up the Node server. Now, run the following command to start the server:
- npm run server
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 then visit the link it shows you to see the application in your web browser.
Note: After /register
and /login
, you may need to clear your localStorage
to force a logout before the session expires to try logging in again or as a different user.
In this guide, you have used vue-router
to define checks on our routes and prevent users from accessing certain routes. You also saw how to redirect users to different parts of your application based on the authentication state. Finally, you built a mini server with Node.js to handle user authentication.
What you did is an example of how access control is designed in frameworks like Laravel. You can check out out vue-router and see what else you can do with it.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Can you add a little information about Logout?
What happen if you manually create a key value pair
jwt : trustMeImAToken
in localstorage and browse ‘/’.