Storyblok Raises $80M Series C - Read News

What’s the True Total Price of Enterprise CMS? Find out here.

Skip to main content

How to add Auth0 Authentication to a Vue.js Application in 7 steps.

Try Storyblok

Storyblok is the first headless CMS that works for developers & marketers alike.

We've read multiple articles in the past on how to use Vue.js with Auth0, most of them were outdated or used JavaScript in a way we wouldn't. In this article, we will first guide you through the setup with a fresh initialized Vue.js application and how to setup your own Authentication plugin for Vue.

Environment Setup

This tutorial will use a freshly created Vue.js project. The newest version of Vue.js by the time of writing this tutorial is 2.5.13 with the newest version of the @vue/cli 3.0.

        
      # installation
npm install -g @vue/cli
# or
yarn global add @vue/cli

# usage
vue create my-project
    

We will use a basic setup including vue-router so you can use the CLI creation step with the setting manually select features and select router as shown in the image below.

Vue CLI Alpha 3

The final step is to boot up your Vue.js application, this can be done by navigating into the project folder and executing the npm script serve as shown we did below:

        
      cd my-project
npm run serve
    

I'll not go deeper into how to use Vue.js and/or the CLI, since all you need to know is already covered in their documentation. After executing npm run serve you should already see a basic setup opened in your default browser as we display in the following image.

New Vue CLI Startup

Prepare Auth0

In order to get started with the setup of the Auth0 + Vue.js Authentication service, we will need our Auth0 account to be ready for us to use.

1. Create your new Account

The first is kinda clear. You can register in Auth0 for the free tier by simply filling their registration form and you're ready to go.

New Auth0 Account

2. Create a new Client

You will find yourself on the dashboard of Auth0 which looks quite familiar for every developer because you already see a heat map as we know it from Github. On the right side, you will see the New Client button which will guide you through the next setup.

Auth0 new SAP client

3. Using the Quickstart to get your client settings

The Auth0 quickstart is nice to have and is used as a basis for this tutorial, sadly it was not a tutorial written especially for Vue.js, but we will still use it to receive our client settings without to look it up manually. At What technology are you using for your web app? simply press the Vue icon and scroll down to Create an Authentication Service and you should find your client settings. Simply copy that and save it for later, since we will use it during the Vue.js Plugin creation.

Auth0 Quickstart Settings

4. Preparing the Callback URL in the Client Settings

At the top of your current page, you should see the tab Settings right below the name of your newly created Client. In those settings, we will have to add http://localhost:8080/callback as allowed Callback URL. You can add other Callback URLs with your own domain by comma-separating them (typically to handle different environments like QA or testing). Make sure the press Save Changes at the bottom of the screen, which can easily be overlooked.

Auth0 Callback Urls

Auth0 Callback Route

You may have noticed that the callback URL we've entered is without the # in front of the route, to allow this we can simply add mode: 'history' to our src/router.js.

        
      import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'
import Callback from '@/views/Callback.vue'

Vue.use(Router)

export default new Router({
  mode: 'history', // enable history mode
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/callback',
      name: 'callback',
      component: Callback
    }
  ]
})
    

Since we won't have a /about route we will simply rename it to /callback as we did already above, in the same step we will also exchange the content of our now src/views/Callback.vue to the following:

        
      <template>
  <div class="callback">Callback</div>
</template>

<script>
export default {
  name: 'callback',
  mounted() {
    this.$auth.handleAuthentication().then((data) => {
      this.$router.push({ name: 'home' })
    })
  }
}
</script>
    

You will notice that we're using this.$auth which currently does not exist - to allow us to access that functionally and finally create the bridge between Auth0 and Vue.js we will now add our own Vue.js plugin to achieve that.

Setup Auth0 + Vue.js Auth Plugin

We will use Auth0 and their Hosted Login Page in combination with their npm module auth0-js, therefore we will start by installing that module.

        
      npm install --save auth0-js
# or
yarn add auth0-js
    

Update (24.04.2018): Make sure you've auth0-js greater than 9.3.0 since known vulnerability are fixed in that version.

Next, we will create a new file src/auth.js with the following content:

        
      import auth0 from 'auth0-js'
import Vue from 'vue'

// exchange the object with your own from the setup step above.
let webAuth = new auth0.WebAuth({
  domain: 'your_auth0_domain',
  clientID: 'your_auth0_client',
  // make sure this line is contains the port: 8080
  redirectUri: 'http://localhost:8080/callback',
  // we will use the api/v2/ to access the user information as payload
  audience: 'https://' + 'your_auth0_domain' + '/api/v2/', 
  responseType: 'token id_token',
  scope: 'openid profile' // define the scopes you want to use
})

let auth = new Vue({
  computed: {
    token: {
      get: function() {
        return localStorage.getItem('id_token')
      },
      set: function(id_token) {
        localStorage.setItem('id_token', id_token)
      }
    },
    accessToken: {
      get: function() {
        return localStorage.getItem('access_token')
      },
      set: function(accessToken) {
        localStorage.setItem('access_token', accessToken)
      }
    },
    expiresAt: {
      get: function() {
        return localStorage.getItem('expires_at')
      },
      set: function(expiresIn) {
        let expiresAt = JSON.stringify(expiresIn * 1000 + new Date().getTime())
        localStorage.setItem('expires_at', expiresAt)
      }
    },
    user: {
      get: function() {
        return JSON.parse(localStorage.getItem('user'))
      },
      set: function(user) {
        localStorage.setItem('user', JSON.stringify(user))
      }
    }
  },
  methods: {
    login() {
      webAuth.authorize()
    },
    logout() {
      return new Promise((resolve, reject) => { 
        localStorage.removeItem('access_token')
        localStorage.removeItem('id_token')
        localStorage.removeItem('expires_at')
        localStorage.removeItem('user')
        webAuth.logout({
          returnTo: 'http://SOMEWHERE.ELSE.com', // Allowed logout URL listed in dashboard
          clientID: 'your_auth0_client_id', // Your client ID
        })
      })
    },
    isAuthenticated() {
      return new Date().getTime() < this.expiresAt
    },
    handleAuthentication() {
      return new Promise((resolve, reject) => {  
        webAuth.parseHash((err, authResult) => {

          if (authResult && authResult.accessToken && authResult.idToken) {
            this.expiresAt = authResult.expiresIn
            this.accessToken = authResult.accessToken
            this.token = authResult.idToken
            this.user = authResult.idTokenPayload
            resolve()

          } else if (err) {
            this.logout()
            reject(err)
          }

        })
      })
    }
  }
})

export default {
  install: function(Vue) {
    Vue.prototype.$auth = auth
  }
}
    

The code above introduces multiple new functionality we can use in our application. One of them is handleAuthentication which is already used in the src/views/Callback.vue View. It will make use of the parseHash function of the auth0-js client to allow the user authentication. You may also have noticed that we use computed properties with getter and setter. Vue.js computed properties are by default getter-only, but we can also provide a setter when we need it. Now when we run vm.token = authResult.idToken, the setter will be invoked and vm.token and will be updated accordingly in the localStorage.

To use our new Vue + Auth0 plugin simply import it to src/main.js as we do below:

        
      import Vue from 'vue'
import App from '@/App.vue'
import router from '@/router'

import auth from '@/auth'
Vue.use(auth)

Vue.config.productionTip = false

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')
    

User Information: Scope

The scope parameter allows our application to express the desired scope of the access request. In turn, the scope parameter can be used by the authorization server in the response to indicate which scopes were actually granted (if they are different than the ones requested). The scope we will use besides the openid is called profile which allows us to access their user information after they accept, make sure to only ask the user about information you really need - since we're going for profile in this demo to speed up the process I recommend to specify only fields you will use to benefit your users. Make sure to update the copied configuration by adding profile to the scope. You can always read More about scopes.

Adding a Navigation Guard

As the name suggests, the navigation guards provided by vue-router are primarily used to guard navigations either by redirecting it or canceling it. We will use a Global Guard and to do so we will update our src/router.js accordingly.

        
      import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/views/Home.vue'
import Callback from '@/views/Callback.vue'

Vue.use(Router)

const router = new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/callback',
      name: 'callback',
      component: Callback
    }
  ]
})

// very basic "setup" of a global guard
router.beforeEach((to, from, next) => {
  if(to.name == 'callback') { // check if "to"-route is "callback" and allow access
    next()
  } else if (router.app.$auth.isAuthenticated()) { // if authenticated allow access
    next()
  } else { // trigger auth0 login
    router.app.$auth.login()
  }
})

export default router
    

Visit http://localhost:8080

You should now be already redirected to your Auth0 Hosted Login Page. After a quick sign-up with your Google Account or Email + Password (both default and can be changed in Auth0) you should be redirected back to the /callback route of your application – and since that authentication should also already working – back to /. In the next step we will adopt src/views/Home.vue to using some user information.

Auth0 Login Page

Using User Information and News Entries

During handleAuthentication in the src/auth.js you can see that there is the idTokenPayload which contains the user information we received from Auth0. To do so we've prepared a simple Bootstrap 4 Navbar including the received User Image and a logout button and we also display a short newsfeed from Storyblok in this now restricted area of your app.

Let's start with exchanging the content of src/App.vue with the following:

        
      <template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>
    

To easily load data from an API we will add axios to our setup:

        
      npm install --save axios
# or
yarn add axios
    

Accessing User Information

And to finally access the user information we will replace the content of src/views/Home.vue with the code below:

        
      <template>
  <div class="dashboard">
    <nav class="navbar navbar-dark bg-dark">
      <a class="navbar-brand" href="#">
        <img src="https://a.storyblok.com/f/39898/1024x1024/dea4e1b62d/vue-js_logo-svg.png" width="40" height="40">
      </a>
      <div>
        <img :src="$auth.user.picture" width="30" height="30">
        <span class="text-muted font-weight-light px-2">{{$auth.user.name}}</span>
        <button type="button" class="btn btn-outline-secondary btn-sm" @click="$auth.logout()">Logout</button>
      </div>
    </nav>
  
    <div class="jumbotron">
      <div class="container">
        <h1 class="display-4">Hello, {{$auth.user.name}}!</h1>
        <p class="lead">We hope you liked this tutorial and can now start building new astounding projects from this start point. If you're interested in what we're doing besides tech tutorials check out <a href="https://www.storyblok.com">@storyblok</a>.</p>
        <hr class="my-4">
        <p>TBH, I'm sure this project of yours would look great with a landing page filled with content composed in <a href="https://www.storyblok.com">Storyblok</a> 🎉</p>
        
        <p class="lead">
          <a class="btn btn-primary btn-lg" href="https://www.storyblok.com/getting-started" target="_blank" role="button">Getting Started</a>
          <a class="btn btn-secondary btn-lg" href="https://twitter.com/home?status=Have%20a%20look%20at%20%40storyblok%20and%20their%20%40vuejs%20%2B%20%40auth0%20tutorial%3A%20https%3A//www.storyblok.com/tp/how-to-auth0-vuejs-authentication" target="_blank" role="button">Tweet it</a>
        </p>

      </div>
    </div>

    <div class="container">
      <div class="card-columns">

        <a class="card" :href="getStoryLink(story)" target="_blank" v-for="story in stories">
          <img class="card-img-top" :src="story.content.image" :alt="story.content.image_alt">
          <div class="card-body">
            <h5 class="card-title">{{story.content.title}}</h5>
            <p class="card-text">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
          </div>
        </a>
        
      </div>
    </div>

  </div>
</template>

<script>
import axios from 'axios'

export default {
  data () {
    return {
      stories: []
    }
  },
  mounted() {
    axios.get('https://api.storyblok.com/v1/cdn/stories?starts_with=tp&excluding_fields=body&excluding_ids=48471,48547,60491&token=dtONJHwmxhdJOwKxyjlqAgtt').then((res) => {
      this.stories = res.data.stories
    })
  },
  methods: {
    getStoryLink(story) {
      return `https://www.storyblok.com/${story.full_slug}`
    }
  }
}
</script>

<style scoped>
@import url('https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css');

.btn-primary {
  background: #468f65;
  border: 1px solid #468f65;
}
.card {
  text-decoration: none;
  color: #000;
}
</style>

    

Final Result

Well done!

The final result should display a bootstrap jumbotron with your name, some cards, and a navbar. The cards will link to other blog posts of Storyblok as seen above. I hope you found this tutorial interesting and maybe it will help you to get started with your idea a little bit faster. I think that the combination of Auth0 with Vue.js is one of the most efficient ways to get your idea to an initial useable prototype and beyond. You won't have to deal with repetitive tasks for user authentication ever again (no login forms - yes! Of course you can customize those Hosted Login Pages) I'm looking forward to hear your feedback, thoughts and ideas to this tutorial or your project ideas!

Want to get your hands on the newest technologies to revolutionize your projects?

Check out Storyblok!

Author

Dominik Angerer

Dominik Angerer

A web performance specialist and perfectionist. After working for big agencies as a full stack developer he founded Storyblok. He is also an active contributor to the open source community and one of the organizers of Scriptconf and Stahlstadt.js.