Persist Access Token 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
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
- When we login successfully for the first time, we will write the Access Token into the
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 ofnull
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:
We need to adjust the (resolve) function to do two more things:
- Write the Access Token to the
localStorage
- Commit the
updateAccessToken
mutation, passingresponse.data.token
as parameter
We also need to adjust the reject function:
- Commit the
updateAccessToken
mutation, passingnull
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 theLogin Successful
paragraph withv-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.
View and modify the localStorage
Press F12
to open up the Browser development tools.
localStorage in Chrome
localStorage in Firefox
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.