Vue Authentication using GraphQL API

Handling Vue Authentication using GraphQL API


In this tutorial, we will be using Vuex and ApolloClient connected to a GraphQL API to handle authentication in our Vuejs app.

Authentication and Authorization

Authentication and Authorization are most often used interchangeably, but they’re different concepts entirely. Authentication identifies or verifies who a user is while Authorization is validating the routes (or parts of the app) the authenticated user can have access to. In this tutorial, we would just be dealing with local authentication. The most popular way for handling authentication in most modern apps is by using username and password. The flow for implementing this is:

  • User signs up using password and email
  • The user credentials are stored in a database
  • When registration is successful, the user is redirected to the login
  • On successful authentication, the user is granted access to specific resources
  • The user state is stored in any one of the browser storage mediums (localStorage, cookies, session) or JWT.

Prerequisites

You need to have some of the following to work through this tutorial:

  • Node 6 or higher (download here)
  • Yarn (recommended) or NPM (install yarn here)
  • Vue CLI
  • GraphQL Playground app. Download here
yarn global add @vue/cli
  • Knowledge of GraphQL and VueJS  and State Management with Vuex 
  • …and a very inquisitive mind.

Before we kick off

Learn Vue.js and modern, cutting-edge front-end technologies from core-team members and industry experts with our premium tutorials and video courses on VueSchool.io.

Click here to Browse all Courses on VueSchool.io

Dependencies

Vuex – Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated predictably.

GraphQL – GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data.

Vue-Apollo – This library integrates apollo in your Vue components with declarative queries. Apollo is a set of tools and community efforts to help you use GraphQL in your apps.

The GraphQL API

The API was built with

  • Apollo Server for the server
  • GraphQL Schema Definition language for the schema
  • Nodejs, bcrypt and jwt were used to create the resolvers (Mutations, Queries)
  • Sequelize ORM for the database relationships
  • and so on…

This is the repo: Jolaolu/graphql-blog-API. This is the API deployed to Heroku: https://bloggr-api.herokuapp.com/ We would use Vue-Apollo to connect to the API very soon.

Project Setup

Open your terminal and type this commands

vue create apollo-auth && cd apollo-auth && vue add apollo

This command would create the vue project and add the apollo graphql plugins.

Vue Authentication using GraphQL API
Vue Create project

Choose Manually select features.(use arrow keys for navigation and spacebar to select).Select Babel, Router(vue-router), Vuex and Linter/Formatter.

cd apollo-auth  // change directory to project folder
yarn serve // start the development server. You can also use npm run serve 

The apollo plugin would continue installing immediately after the project installation is complete. Apollo plugin would add apollo.config.js to the project root folder and vue-apollo to the src folder of the project. It also adds Apollo Provider in the main.js; Apollo provider would allow our components access to the Apollo client instance.

During Apollo installation, you would be prompted to answer some questions, you can use my config or just No to everything (it would still work properly).

The Code


Let’s work on the User Interface for our app. We would need 3 views in our app;

  • Dashboard,
  • Login form
  • SignUp form

First, let’s setup the page layout.
Go to the src folder and open App.vue delete and add this:

<template>
  <div id="app">
    <header class="header">
      <div class="app-name">VueGraph Authenticator</div>
        <div v-if="authStatus" id="nav">
          <div> Hi {{user.name}}</div>
          <button class="auth-button" @click="logOut" > Log Out</button>
        </div>
    </header>
    <router-view/>
  </div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
  name: 'App',
  component: {

  },
  data () {
    return {

    }
  },
  methods: {
    logOut: function () {
      this.$store.dispatch('logOut')
        .then(() => this.$router.push('/login'))
    }

  },
  computed: {
    ...mapGetters(['authStatus', 'user'])
  }

}
</script>
<style>

#app {
  font-family: 'Baloo Chettan 2', cursive;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  width: 100%;
}
#nav {
  display: flex;
}
#nav>div{
  color: white;
  margin-top: 1rem;
  margin-right: 2rem;
  font-size: 2rem;
  font-weight: 500;
}
#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>

Let’s write functionality for login, logout and sign up.

Create Login.vue and SignUp.vue files in the view folder

Login.vue

<template>
  <div class="auth">
    <h3>Log In</h3>
    <form action="POST" @submit.prevent="loginUser">
      <label for="email">Email Address</label>
      <input type="email" name="email"  placeholder="jagaban@borgu.com" v-model="authDetails.email" />
      <label for="password">Password</label>
      <input type="password" name="password" placeholder="password" v-model="authDetails.password" />
      <button class="auth-submit">submit</button>

     <p class="auth-text"> Don't have an account? <router-link style="color:rgb(0, 128, 255)" to="/"> Register </router-link> </p>
    </form>
  </div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
  name: 'Login',
  data () {
    return {
      authDetails: {
        email: '',
        password: ''
      }

    }
  },
  methods: {
    ...mapActions(['login']),
    loginUser: function () {
      this.login(this.authDetails)
        .then(() => this.$router.push('/dashboard'))
    }
  }
}
</script>

In the login.vue component, we are importing vuex mapActions and passing login to it. Later in our Vuex store actions, we would create a login action. The loginUser() get our authentication details from the store and if it resolves, redirects the user to the dashboard route.

SignUp.vue

<template>
  <div class="auth">
    <h3>Sign Up</h3>
    <form action="POST" @submit.prevent="registerUser">
      <label for="name"> Name</label>
      <input type="text" name="name"  placeholder="John Doe" v-model="authDetails.name" />
      <label for="email">Email Address</label>
      <input type="email" name="email"  placeholder="jagaban@borgu.com" v-model="authDetails.email" />
      <label for="password">Password</label>
      <input type="password" name="password" placeholder="password" v-model="authDetails.password" />
      <button class="auth-submit">submit</button>

     <p class="auth-text"> Already have an account? <router-link to="/login" style="color:rgb(0, 128, 255)"> Login </router-link> </p>
    </form>
  </div>
</template>
<script>
import { mapActions } from 'vuex'
export default {
  name: 'Register',
  data () {
    return {
      authDetails: {
        name: '',
        email: '',
        password: ''
      }
    }
  },
  methods: {
    ...mapActions(['register']),

    registerUser: function () {
      this.register(this.authDetails)
        .then(() => this.$router.push('/dashboard'))
    }
  }

}
</script>
<style>

</style>

Our signup.vue , we have similar functionality with login.vue, just pulling register from our store actions and invoking it in our registerUser method.

Let’s create the dashboard component. Go to views and create a Home.vue file.

Home.vue

<template>
  <div class="home">
    <main>
      <h1> Hey {{user.name}} you have been authenticated </h1>
      <div>
        <h4> These are your details... </h4>
        <p> Email: {{user.email}}</p>
        <p> Full Name: {{user.name}}</p>
      </div>
    </main>
  </div>
</template>

<script>
// @ is an alias to /src
import { mapGetters } from 'vuex'
export default {
  name: 'Home',
  components: {

  },
  computed: {
    ...mapGetters(['user'])
  }
}
</script>

<style scoped>
  main{
    padding: 2rem;
    display: grid;
    place-content: center;
  }

  h1{
    font-size: 36px;
  }
</style>

Here, we are simply importing getters from our vuex store. MapGetters simply store getters to local computed properties. In our vuex store, the getters has our authenticated user object.

Integrating GraphQL API using Vue Apollo

In this section we integrate the authentication API and just inject the URL to vue apollo.
Firstly, let’s write queries and mutations we would use later in our vuex store.

Mutation.js

import gql from 'graphql-tag'

export const LOGIN_USER = gql`
mutation login ($email: String! $password: String! ){
  login(email: $email password: $password ){
    token
  }
}
`
export const REGISTER_USER = gql`
mutation createUser($name: String! $email: String! $password: String! ) {
    createUser( name: $name, email: $email, password: $password) {
      token
    }
}
`

In our mutation.js, we would export two graphql functions, LOGIN_USER and REGISTER_USER. Login user accepts user email and password and returns the toke, Register user accepts username, email, and password and returns a token too. Let’s try it out with GraphQL Playground.

Vue Authentication using GraphQL API
This is for creating user.

The API endpoint is https://bloggr-api.herokuapp.com/.

Vue Authentication using GraphQL API
This is for login user.

queries.js

import gql from 'graphql-tag'

export const LOGGED_IN_USER = gql`
  query {
    me {
      id
      name
      email
      posts{
        title
      }
    }
  }
`

Now let’s set up our Vue Apollo
You should have vue-apollo.config.js and apollo-config.js in your project directory, if you installed the vue-apollo plugin.

import Vue from 'vue'
import VueApollo from 'vue-apollo'
import { setContext } from 'apollo-link-context'
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client'

// Install the vue plugin
Vue.use(VueApollo)

// Name of the localStorage item
const AUTH_TOKEN = 'apollo-token'

// Http endpoint
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || '<https://bloggr-api.herokuapp.com/>'

// Config

const authLink = setContext(async (_, { headers }) => {
  // Use your async token function here:
  const token = JSON.parse(localStorage.getItem('apollo-token'))
  // Return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token || ''
    }
  }
})

const defaultOptions = {
  // You can use `https` for secure connection (recommended in production)
  httpEndpoint,
  // You can use `wss` for secure connection (recommended in production)
  // Use `null` to disable subscriptions
  wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql',
  // LocalStorage token
  tokenName: AUTH_TOKEN,
  // Enable Automatic Query persisting with Apollo Engine
  persisting: false,
  // Use websockets for everything (no HTTP)
  // You need to pass a `wsEndpoint` for this to work
  websocketsOnly: false,
  // Is being rendered on the server?
  ssr: false,

  // Override default apollo link
  // note: don't override httpLink here, specify httpLink options in the
  // httpLinkOptions property of defaultOptions.
  // link: myLink
  link: authLink

  // Override default cache
  // cache: myCache

  // Override the way the Authorization header is set
  // getAuth: (tokenName) => { }

  // Additional ApolloClient options
  // apollo: { ... }

  // Client local data (see apollo-link-state)
  // clientState: { resolvers: { ... }, defaults: { ... } }
}

// Create apollo client
export const { apolloClient, wsClient } = createApolloClient({
  ...defaultOptions
  // ...options
})
apolloClient.wsClient = wsClient

// Call this in the Vue app file
export function createProvider (options = {}) {
  // Create vue apollo provider
  const apolloProvider = new VueApollo({
    defaultClient: apolloClient,
    defaultOptions: {
      $query: {
        // fetchPolicy: 'cache-and-network',
      }
    },
    errorHandler (error) {
      // eslint-disable-next-line no-console
      console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message)
    }
  })

  return apolloProvider
}

// Manually call this when user log in
export async function onLogin (apolloClient, token) {
  if (typeof localStorage !== 'undefined' && token) {
    localStorage.setItem(AUTH_TOKEN, token)
  }
  if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
  try {
    await apolloClient.resetStore()
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log('%cError on cache reset (login)', 'color: orange;', e.message)
  }
}

// Manually call this when user log out
export async function onLogout (apolloClient) {
  if (typeof localStorage !== 'undefined') {
    localStorage.removeItem(AUTH_TOKEN)
  }
  if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
  try {
    await apolloClient.resetStore()
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log('%cError on cache reset (logout)', 'color: orange;', e.message)
  }
}

Let’s begin with setContext

import { setContext } from 'apollo-link-context'

In graphql, a context is value which is provided to every resolver and holds important contextual information like the currently logged in user, or access to a database.
The setContext() takes a function that returns either an object or a promise that returns an object to set the new context of a request. Simply, we use it to add authorization header to our HTTP request.

const authLink = setContext(async (_, { headers }) => {
  // Use your async token function here:
  const token = JSON.parse(localStorage.getItem('apollo-token'))
  // Return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token || ''
    }
  }
})

Next is the defaultOptions object.

const defaultOptions = {
    httpEndpoint, 
    wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql',
    tokenName: AUTH_TOKEN,
    persisting: false,
    websocketsOnly: false,
    ssr: false,  
    link: authLink 
  }

We are passing the httpEndpoint, the tokenName, setting server-side rendering to false and overriding the apollo link.

Then we have these two functions, onLogin and onLogOut.
In the onLogin(), we are passing apolloClient and token as parameters and saying that if localStorage is not undefined and there’s a token, it will append the token to AUTH_TOKEN (the token name in our defaultOptions object).
In the onLogout, we are just removing the token we set in onLogin.

export async function onLogin (apolloClient, token) {
  if (typeof localStorage !== 'undefined' && token) {
    localStorage.setItem(AUTH_TOKEN, token)
  }
  if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
  try {
    await apolloClient.resetStore()
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log('%cError on cache reset (login)', 'color: orange;', e.message)
  }
}
export async function onLogout (apolloClient) {
  if (typeof localStorage !== 'undefined') {
    localStorage.removeItem(AUTH_TOKEN)
  }
  if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient)
  try {
    await apolloClient.resetStore()
  } catch (e) {
    // eslint-disable-next-line no-console
    console.log('%cError on cache reset (logout)', 'color: orange;', e.message)
  }
}

Setup Routing of Views

Open the router folder and replace the code with this in index.js.

import Vue from 'vue'
import VueRouter from 'vue-router'
import store from '../store'

Vue.use(VueRouter)

const routes = [
  {
    path: '/dashboard',
    name: 'Home',
    component: () => import('@/views/Home.vue'),
    meta: { requiresAuth: true }
  },
  {
    path: '/login',
    name: 'Login',
    component: () => import(/* webpackChunkName: "login" */ '@/views/Login.vue')
  },
  {
    path: '/',
    name: 'Register',
    component: () => import(/* webpackChunkName: "register" */ '@/views/Register.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes

})

// Global Route Guards
router.beforeEach((to, from, next) => {
  // Check if the user is logged in
  const isUserLoggedIn = store.getters.isAuthenticated
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!isUserLoggedIn) {
      store.dispatch('logOut')
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next()
  }
})

export default router

Let’s now go through the moving parts in this snippet.
Firstly, we are importing our vuex store, we would need it later for accessing the getters isAuthenticated state.
Secondly, we included a meta field in our route to /dashboard, we want to ensure that the user won’t be able to access /dashboard without getting authenticated. That’s what meta: {requiresAuth: true} is doing.
Finally, we access the meta object by iterating over $route.matched to check for meta fields in route records. Then we get the isAuthenticated state from our getters.

State Management with VueX

Open the store folder and add this to index.js

import Vue from 'vue'
import Vuex from 'vuex'
import { onLogout, apolloClient } from '@/vue-apollo'
import { LOGGED_IN_USER } from '@/graphql/queries'
import { LOGIN_USER, REGISTER_USER } from '@/graphql/mutations'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    token: null,
    user: {},
    authStatus: false
  },
  getters: {
    isAuthenticated: state => !!state.token,
    authStatus: state => state.authStatus,
    user: state => state.user
  },
  mutations: {
    SET_TOKEN (state, token) {
      state.token = token
    },
    LOGIN_USER (state, user) {
      state.authStatus = true
      state.user = { ...user }
    },
    LOGOUT_USER (state) {
      state.authStatus = ''
      state.token = '' && localStorage.removeItem('apollo-token')
    }
  },
  actions: {
    async register ({ commit, dispatch }, authDetails) {
      try {
        const { data } = await apolloClient.mutate({ mutation: REGISTER_USER, variables: { ...authDetails } })
        const token = JSON.stringify(data.createUser.token)
        commit('SET_TOKEN', token)
        // onLogin(apolloClient, user.token)
        localStorage.setItem('apollo-token', token)
        dispatch('setUser')
      } catch (e) {
        console.log(e)
      }
    },
    async login ({ commit, dispatch }, authDetails) {
      try {
        const { data } = await apolloClient.mutate({ mutation: LOGIN_USER, variables: { ...authDetails } })
        const token = JSON.stringify(data.login.token)
        commit('SET_TOKEN', token)
        localStorage.setItem('apollo-token', token)
        dispatch('setUser')
      } catch (e) {
        console.log(e)
      }
    },
    async setUser ({ commit }) {
      const { data } = await apolloClient.query({ query: LOGGED_IN_USER })
      commit('LOGIN_USER', data.me)
    },
    async logOut ({ commit, dispatch }) {
      commit('LOGOUT_USER')
      onLogout(apolloClient)
    }
  }

})

Let’s go through the moving parts of the this code snippet.
Firstly, we import, onLogout and apolloClient from our vue-apollo config, then we get the loggedIn user query from our graphql queries and the mutations for login and register user.

import { onLogout, apolloClient } from '@/vue-apollo'
import { LOGGED_IN_USER } from '@/graphql/queries'
import { LOGIN_USER, REGISTER_USER } from '@/graphql/mutations'

Next, we define initial state values.

 state: {
    token: null,
    user: {},
    authStatus: false
  },

Then, set up some useful getters:

 getters: {
    isAuthenticated: state => !!state.token,
    authStatus: state => state.authStatus,
    user: state => state.user
  },

The isAuthenticated field here is just a way to seperate data from app logic and making our getter future proof. It’s just best practice.

Next, we would write mutations:

  mutations: {
    SET_TOKEN (state, token) {
      state.token = token
    },
    LOGIN_USER (state, user) {
      state.authStatus = true
      state.user = { ...user }
    },
    LOGOUT_USER (state) {
      state.authStatus = ''
      state.token = '' && localStorage.removeItem('apollo-token')
    }
  },

SET_TOKEN mutation has token as the payload and assigns the token to state.token. LOGIN_USER sets authStatus to true and LOGOUT_USER removes the localStorage item. Pretty straight forward. New to mutations, check here.

Finally, we write our actions for register, login and setUser :

  actions: {
    async register ({ commit, dispatch }, authDetails) {
      try {
        const { data } = await apolloClient.mutate({ mutation: REGISTER_USER, variables: { ...authDetails } })
        const token = JSON.stringify(data.createUser.token)
        commit('SET_TOKEN', token)
        // onLogin(apolloClient, user.token)
        localStorage.setItem('apollo-token', token)
        dispatch('setUser')
      } catch (e) {
        console.log(e)
      }
    },
    async login ({ commit, dispatch }, authDetails) {
      try {
        const { data } = await apolloClient.mutate({ mutation: LOGIN_USER, variables: { ...authDetails } })
        const token = JSON.stringify(data.login.token)
        commit('SET_TOKEN', token)
        localStorage.setItem('apollo-token', token)
        dispatch('setUser')
      } catch (e) {
        console.log(e)
      }
    },
    async setUser ({ commit }) {
      const { data } = await apolloClient.query({ query: LOGGED_IN_USER })
      commit('LOGIN_USER', data.me)
    },
    async logOut ({ commit, dispatch }) {
      commit('LOGOUT_USER')
      onLogout(apolloClient)
    }
  }

You probably already know that actions commit mutations and contain async operations. Check here.

In our register function, we pass authDetails as an argument here. AuthDetails is actually an object that has the username, email and password and we use the spread operator to get the content of that object and assign it to the variables object of our apolloClient mutation. To learn more about apolloClient mutations, check here. The login function has similar functionality.

async register ({ commit, dispatch }, authDetails) {
      try {
        const { data } = await apolloClient.mutate({ mutation: REGISTER_USER, variables: { ...authDetails } })
        const token = JSON.stringify(data.createUser.token)
        commit('SET_TOKEN', token)
        // onLogin(apolloClient, user.token)
        localStorage.setItem('apollo-token', token)
        dispatch('setUser')
      } catch (e) {
        console.log(e)
      }
    },

Summary

Screenshots of the working App

You can go ahead and deploy it to Netlify or any other CDN provider. The full source code is on Github. If you’re new to client-side graphql with vue, check my previous article here


Share on social media

//