Image

token-based-authentication

7 min read
Last update: December 19, 2021

Project Structure

In this lesson we’ll be looking at the starting code for the example app we’ll be using for this course. We’ll understand the steps that need to be taken to add authentication to it, which we’ll do lesson by lesson throughout the course. We will be using Vue Router, Vuex, and Axios, so if you are not yet familiar with those topics, please take our Real World Vue and Mastering Vuex courses, then meet me back here.


Exploring the Starting Code

If you’d like to code along during this course, you can download the starting code in the Lesson Resources located on this page. Once you’re ready, we’ll start in the main.js file.

📃main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

There’s nothing new here, but note that we’re using Vue Router and Vuex (router and store).


Now let’s look at the App.vue file.

📃App.vue

<template>
    <div id="app">
        <app-nav />
        <router-view class="page" />
    </div>
</template>
<script>
    import AppNav from './components/AppNav'
    export default {
        components: {
            AppNav
        }
    }
</script>
<style lang="scss">
    @import './assets/styles/global.scss';

    .page {
        display: flex;
        justify-content: center;
        flex-direction: column;
        align-items: center;
        min-height: calc(100vh - 56px);
    }
</style>

In the style section you can see we’re importing a global stylesheet from our assets directory and have imported the AppNav component, which we’re using in the template.


Now let’s look at the AppNav component.

📃 src/components/AppNav.vue

<template>
    <div id="nav">
        <router-link to="/">
            Home
        </router-link>
        <router-link to="/dashboard">
            Dashboard
        </router-link>
    </div>
</template>

<script>
    export default {}
</script>

<style lang="scss" scoped>
    #nav {
        display: flex;
        align-items: center;
        min-height: 50px;
        padding: 0.2em 1em;
        background: linear-gradient(to right, #16c0b0, #84cf6a);
    }

    .nav-welcome {
        margin-left: auto;
        margin-right: 1rem;
        color: white;
    }

    a {
        font-weight: bold;
        color: #2c3e50;
        margin: auto 0.8em auto 0.4em;
        text-decoration: none;
        border-top: 2px solid transparent;
        border-bottom: 2px solid transparent;
    }

    .router-link-exact-active {
        color: white;
        border-bottom: 2px solid #fff;
    }

    button,
    .button {
        margin-left: auto;
        background: white;
        text-decoration: none;
        color: #2c3e50;

        &.router-link-active {
            color: #2c3e50;
        }
    }

    .logoutButton {
        cursor: pointer;
    }

    .nav-welcome+button {
        margin-left: 0;
    }
</style>

This file simply has two router-links to our views, and some scoped styles.


Also in the components directory we have the EventCard.vue file, which we’ve seen in previous courses.

📃 src/components/EventCard.vue

<template>
    <div class="event-card">
        <span>@{{ event.time }} on {{ event.date }}</span>
        <h4>{{ event.title }}</h4>
    </div>
</template>
<script>
    export default {
        name: 'EventCard',
        props: {
            event: {
                type: Object,
                default: () => {
                    return {}
                }
            }
        }
    }
</script>
<style scoped>
    .event-card {
        width: 13em;
        margin: 1em auto 1em auto;
        padding: 1em;
        border: solid 1px #2c3e50;
        cursor: pointer;
        transition: all 0.2s linear;
    }

    .event-card:hover {
        transform: scale(1.01);
        box-shadow: 0 3px 12px 0 rgba(0, 0, 0, 0.2), 0 1px 15px 0 rgba(0, 0, 0, 0.19);
    }

    .event-card h4 {
        font-size: 1.4em;
        margin-top: 0.5em;
        margin-bottom: 0.3em;
    }
</style>

This component expects an event prop, which it displays in its template.


Now let’s head into our views directory, where we have the Dashboard.vue and Home.vue files.

📃 src/views/Home.vue

<template>
    <div class="home">
        <h1>Welcome to the App!</h1>
    </div>
</template>

<script>
    export default {}
</script>

The Home view is currently very simple, with just an h1. The Dashboard is where the action is happening.

📃 src/views/Dashboard.vue

<template>
    <div>
        <h1>Dashboard</h1>
        <template v-if="!isLoading">
            <EventCard v-for="event in events" :key="event.id" :event="event" />
        </template>
        <p v-else>
            Loading events
        </p>
    </div>
</template>

<script>
    import axios from 'axios'
    import EventCard from '../components/EventCard'
    export default {
        components: {
            EventCard
        },
        data() {
            return {
                isLoading: true,
                events: []
            }
        },
        created() {
            axios.get('//localhost:3000/dashboard').then(({
                data
            }) => {
                this.events = data.events.events
                this.isLoading = false
            })
        }
    }
</script>

Here, we are importing axios, and using it when the component is created to make a call out to our api, which returns a list of events. We then set the Dashboard’s component data equal to the response. We are also changing isLoading to false. We use the boolean value of isLoading in the template to determine whether to v-for through an EventCard for each event in our events data or to display a loading message instead.


If we look at our router file, we can see we’re importing both Home and Dashboard and have a route for each of them, respectively.

📃router.js

import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import Dashboard from './views/Dashboard.vue'
Vue.use(Router)
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/dashboard',
name: 'dashboard',
component: Dashboard
}
]
})
export default router

Peeking into our store file, we’ll see it’s currently blank.

📃 store.js

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

Vue.use(Vuex)

export default new Vuex.Store({
state: {},
mutations: {},
actions: {}
})

We’ll be adding to Vuex as we build out the app.


Also, notice how we have a server.js file that makes use of the two files in our db directory. Our api will get events from the events.json, and we’ll use the user.json file to register and log in users. Please know that this server-side code is a simple solution meant only for this course. The backend code is not meant for a real production-level application. This course focuses on how to develop an Vue.js authentication front-end, which should work with your backend solution of choice.

Finally, in the package.json, you’ll see I’ve added a start script, which runs our server and builds our app. We’ll type npm run start in the terminal to get our project up and running.


Understanding the Tasks Ahead

Now that we have explored the app, we can start adding authentication to it. But we need to take a step back and understand what steps we’ll be taking to make that happen.

Client/Server Communication

We’ll need to understand the communication that should happen between the client and our server. Our server has three api endpoints: /register, /login, and /dashboard.

We’ll call out to the register endpoint to register our users, then to the login endpoint to log in a registered user. Both of these actions causes the server to return a response that includes a JWT token, which we will send along with our requests to the /dashboard route, where our protected data (events) is returned.

So far so good… but there are three steps that need to be taken between logging a user in and requesting private data.

Handling the Response

When a user registers or logs in, our server will return a response, which includes a JWT token along with the user’s email and name.

We’ll be using Vuex to do three things with that user data:

  1. Store userData in Vuex State
  2. Store userData in local storage (to persist it in case of browser refresh)
  3. Add token to Axios header

We’ll also need to be logging out our user, which will reverse these steps.


What’s next?

Now that we have a foundational understanding of what token-based authentication is and the steps we’ll take to implement it, we are ready to start building. In the next lesson, we’ll add the ability to register users.