WebDevChallenges Logo
WebDevChallenges

Persist Access Token with Vue.js

Updated June 5, 21
Persist the Access Token from the Backend in the localStorage with Vue.js

About

The code is available in a Github repository

Persist Access Token

In the last Post, I explained how to create a Login Component with Vue CLI, SCSS, Axios and Vuex.

The Login Component uses reqres to simulate a RESTful API, we can connect to.

What we will be creating

image

When we use the Login Component to authenticate, you will receive an Access Token from the API (which might look something like this: QpwL5tke4Pnpja7X).

In a realworld application, this Access Token will be used to access protected backend routes (for example a route to edit my own profile) by sending it along to the backend on every request.

It would be a very bad user experience, if the user always has to log in when he refreshes the page.

Therefore we need to persist the token in the frontend (which means, we need to store it somewhere, where we can retrieve it later on)

How we will accomplish this

  • I will build on top of the Login Component created in the previous Post. So make sure to clone it with

git clone https://github.com/WebDevChallenges/vue-login.git

into a directory of your choice, if you want to follow along

  • We will use the localStorage of the user’s browser to store and persist the Access Token (Note: You should use HTTPS in a production application and secure your app for cross-site scription attacks (XSS))
    • When we login successfully for the first time, we will write the Access Token into the localStorage
    • When we load the application (for example through a refresh), we will first of all check if there is a token in the localStorage and if there is, we read it into our Vuex store

Adjusting the store

Lets modify the store first of all.

Adjust the state

  • Remove the state property loginSuccessful
  • Add a state property accessToken with the initial value of null

Now we can determine if the user is authenticated by taking a look at the accessToken value (null = not authenticated, everything else is).

The state should look like this:

state: {
  accessToken: null,
  loggingIn: false,
  loginError: null
},

Adjust the mutations

Adjust the loginStop mutation like this:

  • Remove the loginSuccessful assignment being equal to !errorMessage

Add another mutation called updateAccessToken, which:

  • Sets the accessToken property of the state to the one passed as parameter

The both mutations should look like this now:

loginStop: (state, errorMessage) => {
  state.loggingIn = false;
  state.loginError = errorMessage;
},
updateAccessToken: (state, accessToken) => {
  state.accessToken = accessToken;
}

Adjust the actions

The login action

Inside of the doLogin action, we utilize Axios to send the HTTP POST request to the backend.

Because Axios is Promise based this call returns a Promise. As soon as this Promise resolves (request successful) we will execute a function without taking a parameter: .then(() => {

This was fine because we did not care about the response (Access Token) , the server gave us. But now we want to use it.

Therefore we need to add a parameter to the function like this: .then(response => {

The response object holds all the information about the answer to the request we sent including the data as you can see here:

image

We need to adjust the (resolve) function to do two more things:

  • Write the Access Token to the localStorage
  • Commit the updateAccessToken mutation, passing response.data.token as parameter

We also need to adjust the reject function:

  • Commit the updateAccessToken mutation, passing null as parameter

The doLogin action should look like this:

doLogin({ commit }, loginData) {
  commit('loginStart');

  axios.post('https://reqres.in/api/login', {
    ...loginData
  })
  .then(response => {
    localStorage.setItem('accessToken', response.data.token);
    commit('loginStop', null);
    commit('updateAccessToken', response.data.token);
  })
  .catch(error => {
    commit('loginStop', error.response.data.error);
    commit('updateAccessToken', null);
  })
}

The fetch Access Token action

Next, we want to create another action which will be dispatched every time the application gets loaded.

This action should simply commit the updateAccessToken mutation and pass localStorage.getItem('accessToken') as parameter.

The fetchAccessToken action should look like this:

fetchAccessToken({ commit }) {
  commit('updateAccessToken', localStorage.getItem('accessToken'));
}

The complete store

The store should look like this now:

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    accessToken: null,
    loggingIn: false,
    loginError: null
  },
  mutations: {
    loginStart: state => state.loggingIn = true,
    loginStop: (state, errorMessage) => {
      state.loggingIn = false;
      state.loginError = errorMessage;
    },
    updateAccessToken: (state, accessToken) => {
      state.accessToken = accessToken;
    }
  },
  actions: {
    doLogin({ commit }, loginData) {
      commit('loginStart');

      axios.post('https://reqres.in/api/login', {
        ...loginData
      })
      .then(response => {
        localStorage.setItem('accessToken', response.data.token);
        commit('loginStop', null);
        commit('updateAccessToken', response.data.token);
      })
      .catch(error => {
        commit('loginStop', error.response.data.error);
        commit('updateAccessToken', null);
      })
    },
    fetchAccessToken({ commit }) {
      commit('updateAccessToken', localStorage.getItem('accessToken'));
    }
  }
})

Dispatch the fetch Access Token action

As I mentioned earlier, we want to dispatch the fetchAccessToken action when the app loads initially.

A good place to do this seems to be in the App.vue component created lifecycle hook.

If we map the fetchAccessToken action with ...mapActions, we can simply dispatch the action by calling it like this: this.fetchAccessToken();

The <script> of the App.vue file should look like this:

<script>
import { mapActions } from 'vuex';
import Login from './components/Login.vue'

export default {
  name: 'app',
  components: {
    Login
  },
  methods: {
    ...mapActions([
      'fetchAccessToken'
    ]),
  },
  created() {
    this.fetchAccessToken();
  }
}
</script>

Adjust the Login Component

Lastly we need to adjust the Login Component because we use the loginSuccessful property of the state there, which no longer exists.

  • Simply replace the 'loginSuccessful' in the ...mapState with 'accessToken'
  • Also replace the v-if="loginSuccessful" of the Login Successful paragraph with v-if="accessToken"

Test it

If you run npm run serve and visit http://localhost:8080, you should see the Login Component as usual.

If you enter a valid E-Mail and Password and submit the form, the same success message should be displayed.

But if you now reload the page, the success message should be displayed from the beginning indicating that you are authenticated and the app has an Access Token in the Vuex store which got fetched from the localStorage.

Vuex store of a running application

You can see the state of the Vuex store in a running Vue.js application by using the Browser Plugin Vue.js devtools, which is available for Chrome and Firefox.

Press F12 to open up the Browser development tools.

image

View and modify the localStorage

Press F12 to open up the Browser development tools.

localStorage in Chrome

image


localStorage in Firefox

image

Try playing around with it (for example by deleting the token and reloading the page, you will no longer see the message).

Thank you for reading

I hope this helped you. If it did, make sure to follow me on social media for upcoming posts.