Image

token-based-authentication

9 min read
Last update: December 19, 2021

User Login

Now that we’ve learned how to register users into our app, we can move forward and implement the ability for already-registered users to log into the accounts they’ve created.

In this lesson, we’ll create a new LoginUser.vue component, add some routing to our app to smooth out the user experience, and organize some of our Vuex code.


Creating our Login Component

We are already familiar with creating the RegisterUser component, which took in the user’s name, email and password in order to register them with our app. The LoginUser component will behave very similarly, with some minor differences.

One of the main differences is that we no longer need the user’s name, just their email and password, so our component data will look like this:

📃 src/views/LoginUser.vue

data () {
return {
email: '',
password: ''
}
},

Which means the template will need input elements that are bound to these values, like so:

📃 src/views/LoginUser.vue

<template>
    <div>
        <form @submit.prevent="login">
            <label for="email">
                Email:
            </label>
            <input v-model="email" type="email" name="email" value>

            <label for="password">
                Password:
            </label>
            <input v-model="password" type="password" name="password" value>

            <button type="submit" name="button">
                Login
            </button>
        </form>
    </div>
</template>

The other difference here is instead of calling the register method when the form is submitted, we are calling the login method, which we can write now.

📃 src/views/LoginUser.vue

methods: {
login () {
this.$store
.dispatch('login', {
email: this.email,
password: this.password
})
.then(() => { this.$router.push({ name: 'dashboard' }) })
}
}

This method dispatches the login Vuex action (which we’ll get to in a moment) and sends an object containing the user’s email and password when dispatching. Notice how we are then redirecting to the dashboard route, just like our RegisterUser component does.

Pretty straightforward, huh? So what’s the login Action look like? Let’s head to the Vuex store.js file and write that out.


Vuex: The login Action

The login Action will also behave similarly to how our register Action works. We’ll need it to make an axios post request, but this time to the server’s /login endpoint, sending along the credentials the user input in the LoginUser component.

📃src/store.js

login ({ commit }, credentials) {
return axios
.post('//localhost:3000/login', credentials)
.then(({ data }) => {
commit('SET_USER_DATA', data)
})
}

When the server response is returned, we’re taking that data and passing it in as the payload when we commit the SET_USER_DATA Mutation.

Conceptually, there’s not much new happening here. But let’s get clear on what is actually happening on the server-side when that /login endpoint is hit.


Server Login Behavior

When we post out to our example server’s /login endpoint, this will cause the server to read the user.json file in our mock database, and check if the email and password in that file match the email and password that we just sent as credentials. If they match, a new JWT token is created and sent back within the response data.

This means that when our login Action commits the SET_USER_DATA Mutation, we’re doing exactly what we did in the previous lesson:

  1. Storing the userData in our Vuex State
  2. Storing a copy of the userData in local storage
  3. Adding the token from the userData into our Axios header

Since we’ll then have a JWT token in the axios header, we should be successfully accessing our private event data when we’re redirected to the dashboard route (which calls out for those private events).

We’re almost ready to try this out in the browser, but first we need to add LoginUser into our router.js so we can access it.


Adding LoginUser as a route

We’ll just import the file and add it to the routes array.

📃src/router.js

import LoginUser from './views/LoginUser.vue'
...
routes: [
...
{
path: '/login',
name: 'login',
component: LoginUser
}
]

Testing it out

Now let’s head into our browser and test to see if this all works. We’ll go to localhost:8080/login, which brings us to the login component. We’ll login with whatever email and password is currently saved in the user.json file of our mock database.

(Remember, this backend is not a production-ready solution; it’s only storing one user at a time so you’ll need to log in with the same info you most recently registered with.)

When we do so and hit the login button, we are successfully redirected to the dashboard, which displays our private events.

Great! Now we just have a few more steps remaining for this lesson.


Right now, we have to manually type in a url such as localhost:8080/login or localhost:8080/register. We need to add to our user interface so the user has some links they can click on to route them to these paths.

So let’s head into our Home.vue component and start that process. In our template we’ll add a welcome message, like so:

📃src/views/Home.vue

<div>
    To use this app you'll need to
    <router-link to="/login">
        Login
    </router-link>
    or
    <router-link to="/register">
        Register
    </router-link>
</div>

Now when someone lands on our site’s homepage, they are prompted to login to their account or register to create one.

So far so good, but what if a visitor is at the /login route but they realize they need to create an account? They would have to go back to the home page and then click on the “register” router-link. We’d have the same problem if a visitor is at /register but they need to be at the /login route because they realized they already have an account.

We can add router-links to the bottom of the LoginUser and RegisterUser components to solve this issue and improve our user experience.

📃src/views/LoginUser.vue

<form>
    ...
    <button type="submit" name="button">
        Login
    </button>

    <router-link to="/register">
        Don't have an account? Register.
    </router-link>
</form>

📃src/views/RegisterUser.vue

<form>
    ...
    <button type="submit" name="button">
        Login
    </button>

    <router-link to="/login">
        Already have an account? Login.
    </router-link>
</form>

Now our user has a convenient way to navigate to the route they need. Yay UX!


Adding a Login Button

Additionally, we can add a button to our navbar that prompts a user to login as well. Let’s head into our AppNav.vue component and add a button there.

📃src/components/AppNav.vue

...
<router-link to="/login" class="button">
    Login
</router-link>
...

Now, at the bottom of our template, we have a router-link styled like a button. So if we check the browser, that’ll appear at the top-right corner, like we’ve seen in many web apps before.

Great. Now our app has convenient ways for a user to navigate between our LoginUser and RegisterUser components. But once a user is logged in, they probably shouldn’t be seeing prompts for them to login or register, since they’ve clearly already done that. We can solve for that by using some Vuex State we already have.


If our user is logged in, they probably shouldn’t see router-links prompting them to login. So we can hide those router-links when we know we have a user in our Vuex State.

Technically, we can use this.$store.state.user as an expression to determine whether we display the router-links that prompt logging in/registering, like so:

📃src/views/Home.vue

<template>
    ...
    <template v-if="!this.$store.state.user">
        <div>
            To use this app you'll need to
            <router-link to="/login">
                Login
            </router-link>
            or
            <router-link to="/register">
                Register
            </router-link>
        </div>
    </template>
    ...
</template>

While this works, it’s not the most ideal solution, for a few reasons. We want our templates to be as readable as possible. We might have a designer on our team who doesn’t know Vuex, for example. Also, as our app grows, our Vuex store will evolve. We might break our store into modules and/or rename items in our State, which would require us to update every v-if statement that relies on our Vuex store being structured in a specific way.

A more ideal solution is to use a getter that tells us whether we have a user logged in or not, and make that getter accessible from anyone component that may need it.

So we let’s first create that loggedIn getter:

📃src/store.js

...
getters: {
loggedIn (state) {
return !!state.user
}
}
...

If this !! syntax is new to you, just know it helps us determine the truthiness or falsiness of the value. So this getter will return true if we have a user stored in our state, and false when that state is null.


Now let’s start to make this getter accessible to any component that needs it.

Inside our src directory, we’ll create a new vuex folder, and move our store.js file into it. Inside of that vuex folder, we’ll then create a new helpers.js file. As you might imagine, we can use this file to import Vuex helpers, specifically mapGetters.

📃src/vuex/helpers.js

import { mapGetters } from 'vuex'

export const authComputed = {
...mapGetters(['loggedIn'])
}

Notice how we’re then exporting authComputed, which uses the mapGetters helper to map to the loggedIn getter that we just added to our store.js file.

Now we can import this vuex helper into the components that need it, such as the Home component.

📃src/views/Home.vue

<script>
    import {
        authComputed
    } from '../vuex/helpers.js'

    export default {
        computed: {
            ...authComputed
        }
    }
</script>

After importing, we can add authComputed as a computed property, this component will then have access to everything that lives within authComputed, which includes the loggedIn getter.

So in our template, instead of <template v-if="!this.$store.state.user">, we can use our getter: loggedIn

**📃src/views/Home.vue**

```
<template>
    ...
    <div v-if="!loggedIn">
        To use this app you'll need to
        <router-link to="/login">
            Login
        </router-link>
        or
        <router-link to="/register">
            Register
        </router-link>
    </div>
    ...
</template>
```

Great. Now we will only display this welcome message when we’ve determined that there is not already a user in our State.

---

We can repeat this process in the our **AppNav** component, which also needs to hide a router-link when we have a user logged in.

**📃src/components/AppNav.vue**

```
<template>
    <div id="nav">
        ...
        <router-link v-if="!loggedIn" to="/login" class="button">
            Login
        </router-link>
    </div>
</template>

<script>
    import {
        authComputed
    } from '../vuex/helpers.js'
    export default {
        computed: {
            ...authComputed
        }
    }
</script>
```

Here, we’ve imported `authComputed`, added it as a computed property, and added `v-if="!loggedIn"` to the router-link that we need to display or not.

---

## Let’s ReVue

In this lesson, we covered how to log in users who had already registered with our app, then added conditionally displaying navigational links to smooth out the user experience, relying on some effective Vuex reorganization to provide a more scalable (and readable) solution.

In the next lesson, we’ll cover the process of effectively logging _out_ a user.