Vue.js — Setting up Auth0

In my last post I covered the basics of getting a Vue.js project setup. You can check out that post here. In this post I will cover how to add authorization with Auth0. You can find the completed repo here.

Setting up Auth0

There are many choices for handling security in applications. For me, offloading the details of username / password storage, change password workflows, new user signups, etc makes my app development less complicated. The team at Auth0 have done an incredible job of creating tools that allow you as a developer to add best in class security to your apps with ease. Building on the previous project, let’s setup Auth0 in the app and create a basic login page.

You will need to create an account with Auth0. Once you have your account, create a new client by clicking on the clients menu and then the new client button. That will give you a screen to name the client and choose a project type.

Auth0 — create new client

Once you click create it will take you to a quick start page where you can choose your framework and they will provide a setup guide. This will guide you thru adding the Auth0 to your app using the lock widget. I am going to be using the base Auth0 script and custom login page for this project. The lock widget is a great tool to get up and running quickly and the guide will be the best resource for using it. The main difference will be the login box, the guide will use the lock widget and I will provide a custom form … either one will process the request in a similar manner.

In the Auth0 client dashboard, select the settings tab. This is where you will find your domain and client id. These values will be needed later when making api calls via the Auth0 script. You will also need to set the allowed callback url. For development it will be http://localhost:8080 (if you are using the vue-cli and have not changed the port).

Be sure to click the save settings button at the bottom. You will also want to add a user for testing. Click on the users menu option on the left and then the create user button. Enter the details and click the save button.

Next, let’s add Auth0 to the app. There are several ways to install the package, I will be linking to it via the cdn. In the index.html, add the following line:

<!-- Auth0 -->
<script src="https://cdn.auth0.com/w2/auth0-7.4.min.js"></script>

Setup auth.js

Create a file in the root of /src named auth.js. The auth logic for the entire app will be housed here. First, create an instance of Auth0.

/* eslint no-undef: "off" */
const auth0 = new Auth0({
domain: 'YOUR DOMAIN',
clientID: 'YOUR CLIENT ID',
responseType: 'token',
callbackURL: window.location.origin + '/'
})

You will need to enter your domain and client id details from the settings tab in the Auth0 dashboard. If you used the webpack template from the vue-cli there are config files where you can set these details and point to them via process.env.variable_name, check out the documentation here.

This file will export 4 helper functions that we can use throughout our app:

login / logout / checkAuth / requireAuth

// login
let login = (username, password) => {
auth0.login({
connection: 'Username-Password-Authentication',
responseType: 'token',
email: username,
password: password,
scope: 'openid email'
},
function (err) {
if (err) alert('something went wrong: ' + err.message)
})
}

The login function calls Auth0.login and passes it a config object. Auth0 provides multiple connections for social and enterprise logins but here I am using the username password connection. The response type is set to token indicating that we want to use a JWT (json web token) for our security strategy. The scope variable tells auth0 the details to be encoded in the jwt, depending on your backend api you might need to add additional data to identify the user on the api side.

// logout
let logout = () => {
localStorage.removeItem('id_token')
localStorage.removeItem('profile')
}

The logout function simply removes the id_token and profile items from local storage. These values are set when the login call is successful (more on that shortly).

// checkAuth
let checkAuth = () => {
if (localStorage.getItem('id_token')) {
return true
} else {
return false
}
}

The checkAuth function simply returns true if there is value in the local store with and id of ‘id_token”. For a production app you could consider decoding the token and checking to make sure the value is a jwt token and has not expired. Auth0 offers a jwt-decode library that will aid in this. This library will not validate your token (that happens on the server side) but it can be used to help confirm the token.

// requireAuth
let requireAuth = (to, from, next) => {
if (!checkAuth()) {
console.log('auth failed ...')
let path = '/login'
let result = auth0.parseHash(window.location.hash)
if (result && result.idToken) {
// set token in local storage
localStorage.setItem('id_token', result.idToken)
      // redirect to home page
path = '/'
      // get user profile data
auth0.getProfile(result.idToken, function (err, profile) {
if (err) {
// handle error
alert(err)
}
let user = JSON.stringify(profile)
localStorage.setItem('profile', user)
})
}
next({
path: path
})
} else {
next()
}
}

This function is called from the router beforeEnter hook, it receives a to and from variable and callback variable named next. This allows you a chance to validate the route before the component is loaded. First we call the checkAuth function, if it passes we simply call the next() callback and let things continue on.

If the auth fails, we check the url for a hash and token. The Auth0 service is configured by default to use the re-direct method, so the whole app gets redirected back to the callback url in the Auth0 config. Since this beforeEnter hook is set on our home route, we can check it for the hash and token and set them to our local storage. We also grab the user profile and set that in local storage. One last thing to note in the function, I have variable named path that defaults to /login. If the url has a hash and token, I reset the path the take the user back home (you could also send the redirectUrl to Auth0 and take the user back where they were, for simplicity I am sending back them back to the home page). If the there was no hash or token and the checkAuth failed, the user will be taken to the login page.

Finally, export the functions:

export default {
checkAuth,
login,
logout,
requireAuth
}

Create login container

Now that we have our auth logic setup, let’s create a simple login page. Under the containers folder, add a new file named Login.vue.

<template>
<div id="login">
<div class="columns">
<div class="column is-one-quarter">
<form @submit.prevent="login">
<p class="control">
<input class="input" v-model="email" placeholder="email">
</p>
<p class="control">
<input class="input" v-model="pass" placeholder="password" type="password">
</p>
<p>
<button class="button" type="submit">Login</button>
</p>
</form>
</div>
</div>
</div>
</template>
<script>
import auth from '../auth'
export default {
name: 'login',
data () {
return {
email: '',
pass: ''
}
},
methods: {
login () {
auth.login(this.email, this.pass)
}
}
}
</script>

Here I am using a simple form with inputs bound via v-model. When the form is submitted, the login method simply passes the inputs to the auth login function.

Adjusting the router

Let’s make some adjustments to the router instance to call the requireAuth function in the beforeEnter hook.

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
import login from './containers/Login'
import home from './containers/Home'
import dashboard from './containers/dashboard'
import projects from './containers/projects'
import auth from './auth'
// application routes
const routes = [
{ path: '/login', component: login },
{ path: '/', component: home, beforeEnter: auth.requireAuth },
{ path: '/dashboard', component: dashboard, beforeEnter: auth.requireAuth },
{ path: '/projects', component: projects, beforeEnter: auth.requireAuth }
]
// export router instance
export default new Router({
mode: 'history',
routes,
linkActiveClass: 'is-active'
})

Adjust the navbar

Next, the navbar needs to be updated wiring up the logout button and only showing if the user is logged in. In the navbar.vue component, add the following methods:

methods: {
logout () {
auth.logout()
this.$router.go('/login')
},
isLoggedIn () {
return auth.checkAuth()
}
}

Make sure to import the auth file:
import auth from ‘../auth’

Setup the click event on the button:

<a class="button" @click="logout()">Logout</a>

and finally add a v-if to the top level of the nav:

<nav class="nav has-shadow" v-if="isLoggedIn()">

Authorization headers in api calls

Now that our app is setup to authenticate our users, the app needs to send the token to our api when making secure calls. To make this work, I will set an authorization header on the default header object in axios.

Head back into the auth.js file, and in the section where it sets the token in local storage add the following line:

// set auth headers
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('id_token')

This adds the authorization header to every api call made with axios. In addition to setting the value when the auth token gets set, it needs to get set when the app loads (if there is a token). Add the following lines in the auth.js file, outside of the functions:

// set auth header on start up if token is present
if (localStorage.getItem('id_token')) {
axios.defaults.headers.common['Authorization'] = 'Bearer ' + localStorage.getItem('id_token')
}

Response interceptor

The last item to add to make it secure is to create a response interceptor that will logout our user if an authorized call is returned. In the main.js file, add the following:

import auth from './auth'
import axios from 'axios'
axios.interceptors.response.use((response) => {
return response
}, function (error) {
// Do something with response error
if (error.response.status === 401) {
console.log('unauthorized, logging out ...')
auth.logout()
router.replace('/login')
}
return Promise.reject(error)
})

If the response returns and error and it’s 401, call auth.logout(). The user will then get re-directed to the login page.

Server side check

Just to ensure our auth is working, let’s create a simple express server to test it. Create folder named server and add a file named app.js. You will need to install express, express-jwt and cors. Cors is needed since we are going to run the express server on a different port.

$ yarn add express express-jwt cors --dev
OR 
$ npm install express express-jwt cors --save --dev

I added the dev flag on the above statement to ensure these are development only dependencies. Add the following code to the server/app.js file:

var express = require('express')
var app = express()
var cors = require('cors')
var jwt = require('express-jwt')
var jwtCheck = jwt({
secret: 'YOUR_CLIENT_SECRECT',
audience: 'YOUR_CLIENT_ID'
})
app.use(cors())
// check security for anything under the secured route
app.use('/secured', jwtCheck)
// open call
app.get('/ping', function(req, res) {
res.send("All good. You don't need to be authenticated to call this");
})
// secured call
app.get('/secured/ping', function(req, res) {
res.status(200).send("All good. You only get this message if you're authenticated");
})
app.listen(3000, function () {
console.log('Example app listening on port 3000!')
})

Here I created a simple express server that has 2 routes. The /ping route is un-secured and can be called by anyone. Any path under /secured will get processed thru the jwtCheck function and a valid token must be sent in the header of the request. So if I call /secured/ping I will need to provide a valid token to get a response.

You will need to supply the jwtCheck function 2 variables: secret and audience. The audience is your client id from your client dashboard in Auth0 and the secret is the client secret from the same screen. For a more complete server backend example, check out the Auth0 express example repo here.

Back in the client side of our app, let’s make the final adjustments to test our app. In the main.js file, add the following line to set the baseUrl for axios.

axios.defaults.baseURL = 'http://localhost:3000'

Then in home.vue add a button to call the secured api:

<button type="button" class="button" v-on:click="testSecured()">Test API - Secured</button>
methods: {
testSecured: function () {
console.log('sending secured test call to api ...')
axios.get('/secured/ping').then((response) => {
console.log(response)
}, (response) => {
console.log(response)
})
}
}

Run the app and start the node server [ node server/app.js ], login and test to make sure you can make authenticated calls. The button will log the response to the console:

example secured api call

Conclusion

Now that we have secured we can take the next steps to building out the rest of our screens and functionality. In my next post I will cover setting up vuex and using it to store the user profile data. I will also use to manage the state of our projects page.