Integrating Auth0 with Vue and Vuex

March 26, 2018

While you can find very good documentation on Auth0 site for integrating Auth0 with Vue it’s based on managing state through propagating props and custom events. The more practical and real-world implementation would use Vuex. Therefore I’ve created an example project integrating Auth0 with Vue and Vuex. It’s based on a similar concept as original documentation but uses Vuex for state management and events handling.

TL;DR

Further in this post I’m describing some of the aspects of the integration. If you’re not interested though you can clone the example code from GitHub.

Setup

In this post I assume you’ve already setup an Auth0 account and created the Single Page Web Application client. If not refer to the documentation on how to do it. I also assume you’re familiar with OAuth2 authentication.

For this tutorial you’d need to have npm with vue-cli installed on your machine with:

$ npm i -g @vue/cli

I assume you’re familiar with Vue CLI tool.

Generate new Vue project with:

$ vue create auth0-with-vue-and-vuex

Out of the features dialog you need to pick manual option and select Router and Vuex to install necessary dependencies and configuration.

Finally install the auth0.js library.

$ npm i -S auth0-js

Now we’re ready to go.

Authentication service

The usage of auth0.js is pretty straightforward. Create an instance of auth0.WebAuth providing your client configuration, request type and scope than use authorize method to start login process and parseHash to parse returned tokens. If you’re familiar with OAuth2 you’d know that tokens will be appended to the callback URL to which user is redirected to.

As the Auth0 documentation suggests best way is to encapsulate that logic with reusable service. Let’s create one:

// src/lib/Authenticator.js

import auth0 from 'auth0-js'

export default class Authenticator {
  constructor () {
    this.auth0 = new auth0.WebAuth({
      domain: '',
      clientID: '',
      redirectUri: 'http://localhost:8080/auth',
      audience: '',
      responseType: 'token id_token',
      scope: 'openid'
    })
  }

  login () {
    this.auth0.authorize()
  }
}

You need to provide domain, clientID, redirectUri and audience to your Auth0 client. Auth0 will generate that values for you which can be simply copy’n’pasted to the actual code. The login method initiates the authentication flow by redirecting user to the Auth0 login dialog.

Creating user session store

We need a place to instantiate our Authenticator and where to keep user session information. Since this post is about Vuex let’s go ahead and firstly create a store for user session:

// src/store/modules/session.js

import Authenticator from '@/lib/Authenticator'

const auth = new Authenticator()

const state = {}

const actions = {
  login () {
    auth.login()
  }
}

export default {
  state,
  actions
}

If you created new project with vue-cli you probably have src/store.js file. You can use it as well but I prefer to rename that file to src/store/index.js and use modules for concern separation. Both solutions works fine and it’s the matter of preference which one to use.

Our state is empty for now but we’d fill it up soon. Now we need to add this module to our Vuex store:

// src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

import session from './modules/session'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    session
  }
})

Alright, now we’re ready to fire the authentication flow.

Triggering authentication

To initiate authentication we need to call login action from our store. Typically such action is fired in response to clicking some button or link usually in page top nav bar. Let’s create a Vue component for that:

// src/components/Navbar.vue

<template>
  <nav class="navbar navbar-dark bg-dark">
    <a href="#" class="navbar-brand">Auth0 with Vue and Vuex Example</a>
    <ul class="navbar-nav ml-auto">
      <li class="nav-item">
        <button class="btn btn-primary" @click='login()'>Sign In</button>
      </li>
    </ul>
  </nav>
</template>

<script>
import { mapActions } from 'vuex'

export default {
  name: 'Navbar',

  methods: mapActions(['login'])
}
</script>

When clicking our login button the login method is called which dispatches the login action of our store. This will redirect user to Auth0 login dialog.

Please note using bootstrap. Simplest way of adding it is pasting the CDN link in the index.html file. Alternatively npm package can be installed and imported in App component.

We need to add our Navbar component the the App component:

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

<script>
import Navbar from '@/components/Navbar'

export default {
  components: { Navbar }
}
</script>

Now we can initiate login flow. Go ahead and run npm run serve navigate to http://localhost:8080 and click the Sign In button. You should be redirected to Auth0 login page.

Handling Redirect

First part is done. We’re initiating authentication flow and redirecting user to login dialog managed by Auth0. Next step is to handle redirect callback. Auth0 would append auth_token and id_token to the redirect URL we’ve set in the initial request (http://localhost:8080/auth in our case). We should validate those tokens to ensure they’re generated by the trusted entity. Fortunatelly the WebAuth object we’ve instantiated in Authenticator has a build in method for that. So let’s start with extending our authentication service:

export default class Authenticator {
  // ...

  handleAuthentication () {
    return new Promise((resolve, reject) => {
      this.auth0.parseHash((err, authResult) => {
        if (err) return reject(err)

        resolve(authResult)
      })
    })
  }
}

The parseHash from the auth0.js library would validate and parse tokens and expires_in which determines the duration of the access token. Since it provides the results in a callback the easiest way would be to return a promise so that it’s easier to use that in store action which we’re going to add now by extending our session store:

import Authenticator from '@/lib/Authenticator'

const auth = new Authenticator()

const state = {
  authenticated: !!localStorage.getItem('access_token'),
  accessToken: localStorage.getItem('access_token'),
  idToken: localStorage.getItem('id_token'),
  expiresAt: localStorage.getItem('expires_at')
}

const getters = {
  authenticated (state) {
    return state.authenticated
  }
}

const mutations = {
  authenticated (state, authData) {
    state.authenticated = true
    state.accessToken = authData.accessToken
    state.idToken = authData.idToken
    state.expiresAt = authData.expiresIn * 1000 + new Date().getTime()

    localStorage.setItem('access_token', state.accessToken)
    localStorage.setItem('id_token', state.idToken)
    localStorage.setItem('expires_at', state.expiresAt)
  },

  logout (state) {
    state.authenticated = false
    state.accessToken = null
    state.idToken = false

    localStorage.removeItem('access_token')
    localStorage.removeItem('id_token')
    localStorage.removeItem('expires_at')
  }
}

const actions = {
  login () {
    auth.login()
  },

  logout ({ commit }) {
    commit('logout')
  },

  handleAuthentication ({ commit }) {
    auth.handleAuthentication().then(authResult => {
      commit('authenticated', authResult)
    }).catch(err => {
      console.log(err)
    })
  }
}

export default {
  state,
  getters,
  mutations,
  actions
}

That’s the full implementation of our session store. In that store we have login, logout and handleAuehtneication actions which will set through mutations the state of our session including storing it in local storage.

Now we need to setup a route which would handle our callback redirect:

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

Finally we need Auth component:

// src/views/Auth.vue

<template>
  <div class="authenticating">
    Authenticating...
  </div>
</template>

<script>
import router from '@/router'
import { mapActions } from 'vuex'

export default {
  name: 'Auth',

  methods: mapActions(['handleAuthentication']),

  data () {
    this.handleAuthentication()
    router.push({ name: 'home' })

    return {}
  }
}
</script>

The Auth component will handle user redirect and dispatch handleAuthentication action to validate and parse tokens. Finally it redirects to the /home path.

Technically authentication is finished at this point but we have no UI indicator that tells whether user is authenticated and no way to destroy the session. Let’s update the Navbar component to address that:

<template>
  <nav class="navbar navbar-dark bg-dark">
    <a href="#" class="navbar-brand">Auth0 with Vue and Vuex Example</a>
    <ul class="navbar-nav ml-auto">
      <li class="nav-item">
        <button v-if="!authenticated" class="btn btn-primary" @click='login()'>Sign In</button>
        <a v-if="authenticated" href="#" class='nav-link' @click='logout()'>Log Out</a>
      </li>
    </ul>
  </nav>
</template>

<script>
import { mapActions, mapGetters } from 'vuex'

export default {
  name: 'Header',

  methods: mapActions(['login', 'logout']),

  computed: mapGetters(['authenticated'])
}
</script>

That is the final version of Navbar component which will render Sign In button and Log Out link when user is authenticated. Also would dispatch logout action to destroy user session.

Go ahead and open http://localhost:8080. The authentication is fully functional at this point.

blog comments powered by Disqus