Image

token-based-authentication

8 min read
Last update: December 19, 2021

User Logout

Now that we understand how to log users into our app, we can start thinking about what it takes to successfully log a user out of our app. As you might expect, logging a user out will essentially require us to reverse the steps we took when we logged a user in. This means we’ll need to remove the user data from our Vuex State, remove the user data from our local storage, and take the JWT token out of our axios Authorization header. Let’s get started adding all of this functionality.


Creating a Logout Button

First, we need to add to our user interface. We’ll add a simple way for a user to log out of our app. Just like we have a Login button that shows up in our navbar when a user is not logged in, we can add a Logout button to our navbar, which shows up when a user is logged in. Let’s head into our AppNav component and add that button now.

src/components/AppNav.vue

<router-link v-if="!loggedIn" to="/login" class="button">
    Login
</router-link>

<button v-else type="button" class="logoutButton" @click="logout">
    Logout
</button>

Notice how we placed the Logout button just below the Login button. We did this so that we could simply add a v-else onto it so it will show up whenever the Login button is not showing up. In other words: the Logout button will show up whenever a user is logged in. In addition to having a logoutButton class, our button is also triggering a method with @click="logout" So what does that logout method need to do? We’ll want it to dispatch a Vuex Action called logout, so let’s add that method now inside our AppNav component.

📄 src/components/AppNav.vue

methods: {
logout () {
this.$store.dispatch('logout')
}
}

Now we can head into our Vuex store and get this logout behavior working.


Adding to the Store

Let’s add our logout Action. 📄 src/vuex/store.js

logout ({ commit }) {
commit('LOGOUT')
}

As you can see, this simply commits the LOGOUT Mutation. Before we write that Mutation, let’s pause a moment to think about what needs to happen inside of it. Like I mentioned earlier, when we log a user out of the app, we are essentially reversing the login steps. Or in other words, we are reversing what we accomplished with the SET_USER_DATA Mutation, which looks like this:

📄 src/vuex/store.js

SET_USER_DATA (state, userData) {
state.user = userData
localStorage.setItem('user', JSON.stringify(userData))
axios.defaults.headers.common['Authorization'] = `Bearer ${
userData.token
}`
}

It’s setting the userData into our user State, setting the userData in local storage, and setting the token as the axios Authorization header. So to reverse this, we could write something like:

📄src/vuex/store.js

LOGOUT (state) {
state.user = null
localStorage.removeItem('user')
axios.defaults.headers.common['Authorization'] = null
}

But there is actually a simpler and more scalable way to achieve this, which looks like:

📄src/vuex/store.js

LOGOUT () {
localStorage.removeItem('user')
location.reload()
}

Here, we are still manually removing the user from our local storage, but we’re using location.reload() to handle clearing out the Vuex State and axios header. If you’re not familiar with this reload method, it is essentially forcing a refresh of our page. And since Vuex State does not survive a browser refresh, it takes care of clearing our user State for us. Additionally, it clears our axios Authorization header as well.

So with one forced refresh, we handled both steps we needed, and have proactively built-in some scalability here. How? Well if you imagine our app growing, we’d probably break our Vuex into modules. This means we’d likely need to rewrite the way we are clearing our user State, which eventually may not live in the same file as the Mutation that is trying to clear it. So reload() is a handy trick to save us some headache down the line.


Checking the browser

If we check this out in the browser, we’ll see that when we click the Logout button, it works as expected. And if we’re on the Dashboard route, we’ll see that the page displays “Loading events…” and the console gives us a 401 error (which our mock server sent back since it did not receive the correct request credentials). So far so good! But it doesn’t make sense for us to allow a user who is not logged in to access this Dashboard route, since they won’t have the proper permissions to view the events that we display here anyway. So we can head into our router.js file and make sure that when a user logs out (or was never logged in to begin with) they can’t access our Dashboard, or any other private route within our app.


Blocking private routes

In order to block access to a private route via Vue Router, we can use a navigation guard, which we learned about in our Next-Level Vue course. Our first step will be to make use of the meta tag, which can be used to add protections to a given route. So let’s make it so that the dashboard route requiresAuth.

📄 src/router.js

{
path: '/dashboard',
name: 'dashboard',
component: Dashboard,
meta: { requiresAuth: true }
},

Now we can start building out our beforeEach method, which receives three arguments: to : the route being navigated to from: the current route being navigated away from next the function called to resolve the hook

📄src/router.js

router.beforeEach((to, from, next) => {
// now what?
})

What do we need to happen inside this guard? First, we’ll need to know a couple things:

  1. Do we have a user logged in?
  2. Does the route being navigated to requireAuth?

So let’s handle step one. We can find that out by simply checking our local storage, like so:

📄 src/router.js

router.beforeEach((to, from, next) => {
const loggedIn = localStorage.getItem('user')
})

Now we can check if the route being navigated to requiresAuth:

📄 src/router.js

router.beforeEach((to, from, next) => {
const loggedIn = localStorage.getItem('user')

if (to.matched.some(record => record.meta.requiresAuth)) {
// now what?
}
})

Here we are saying if the route being navigated to matches one of our routes (record) where the meta tag includes requiresAuth = true, then we’ll do something. What do we want to do now? We need to see if we have a user loggedIn. If not, then we will redirect to the home route and return the function.

📄 src/router.js

router.beforeEach((to, from, next) => {
const loggedIn = localStorage.getItem('user')

if (to.matched.some(record => record.meta.requiresAuth)) {
if (!loggedIn) {
next('/')
return
}
next()
}
})

Otherwise, the function continues to run and we’ll hit the second next() and fulfill that route request. And we need to add a third next() outside of the initial if statement so that we can fulfill the route request if the route is simply public.

📄src/router.js

router.beforeEach((to, from, next) => {
const loggedIn = localStorage.getItem('user')

if (to.matched.some(record => record.meta.requiresAuth)) {
if (!loggedIn) {
next('/')
return
}
next()
}

next()
})

If you’re looking at this code and wondering, “Can’t this be done with less code?” You’re right! This is how we could simplify it further:

📄src/router.js

router.beforeEach((to, from, next) => {
const loggedIn = localStorage.getItem('user')
if (to.matched.some(record => record.meta.requiresAuth) && !loggedIn) {
next('/')
}
next()
})

Great. Now every time we navigate to a new route ( beforeEach ), we are checking if that route requires authentication and if we have a user logged in. If it does require authentication and we don’t have a user, we’ll redirect to our home route. Otherwise, we can fulfill that route request. We’re almost done, there’s just one little feature we need to add.


Hiding the Dashboard Link

If we check this out in the browser, we can see when we hit the Logout button, not only are we logged out, but since we no longer have the permission to access the guarded Dashboard route, we are redirected to the home page. But we can still see the Dashboard link in the navbar. Even though that link won’t take us to the guarded route when we click on it, that’s even more of a reason to hide it when a user is logged out. So let’s head into our AppNav component and make sure it won’t show up when it shouldn’t.

📄src/components/AppNav.vue

<router-link v-if="loggedIn" to="/dashboard">
    Dashboard
</router-link>

By simply adding v-if="loggedIn", we will show this button only when a user is logged in. This is similar to the previous lesson, where we hid the Login button whenever a user was not logged in.


Let’s ReVue

In this lesson, we successfully added the functionality to log a user out of our app, which means we reversed our login steps, and we even learned how to use the reload method to make this logout functionality more scalable from the get-go. In the next lesson, we’ll learn how to handle the errors that happen when people attempt to log into our app incorrectly.