API Driven Development With Laravel and VueJS (Part 34 of 38)

VueJS Route Security and Authentication

Dan Pastori

January 4th, 2018

In our last tutorial https://serversideup.net/public-private-api-laravel/, we converted the Laravel API Backend to be accessible for both an authenticated user and an unauthenticated user. This tutorial will go through the process of securing pieces of data that we don't want the user to to view if they are not authenticated.

Before we make any crazy claims it's very possible for users to just inject Javascript and tweak all sorts of things. We don't want to rely 100% on the security of front end JS to secure our data. We just want to block actions from users. Our server and API will handle the hardcore security like preventing users from inserting or updating data.

Step 1: Add Redirect so Layout is Never in View

We are using the / route in VueJS to act as a global layout for all of our other routes. However, this is still a navigable route. We want to redirect this to our cafes home page.

Open up the /resources/assets/js/routes.js file and add the redirect key to your routes object underneath the path we are using for layout:

Route configuration with redirect

{
  path: '/',
  redirect: { name: 'home' },
  name: 'layout',
  component: Vue.component( 'Layout', require( './pages/Layout.vue' ) ),
  children: [

Now whenever anyone visits this page, they are redirected to the home screen.

Step 2: Create Modal for Logging In

Since we removed the login route from the PHP side in the last tutorial https://serversideup.net/public-private-api-laravel/, we are now allowing our social login through a modal that we can call from anywhere. This way every thing is contained in our single page application.

First, let's create the /resources/assets/js/components/global/LoginModal.vue component and enter the basic component stub:

Basic LoginModal component structure

<style lang="scss">

</style>

<template>

</template>

<script>
  import { EventBus } from '../../event-bus.js';

  export default {

  }
</script>

This component will essentially just provide links to our /login/{social} url. The structure, we will have copied from our now deleted login.blade.php. First, let's fill out the template with:

LoginModal template with social login links

<template>
  <div id="login-modal" v-show="show" v-on:click="show = false">
    <div class="login-box">
        <a href="/login/google" v-on:click.stop="">
          <img src="/img/google-login.svg"/>
        </a>

        <a href="/login/twitter" v-on:click.stop="">
          <img src="/img/twitter-login.svg"/>
        </a>

        <a href="/login/facebook" v-on:click.stop="">
          <img src="/img/facebook-login.svg"/>
        </a>
    </div>
  </div>
</template>

What this does is wrap a login-box element with a login-modal element. Each link in the login box allows the user to log in with whatever social network that they want similar to what we had before. One thing to note about those links is they have an attribute of v-on:click.stop="". What this does is stop the propagation so the event stops after the user clicks. This is because the login-modal will close on a click and we don't want the modal to close on a child click.

Now let's add our functionality:

LoginModal component script with event handling

<script>
  import { EventBus } from '../../event-bus.js';

  export default {
    data(){
      return {
        show: false
      }
    },

    mounted(){
      EventBus.$on('prompt-login', function(){
        this.show = true;
      }.bind(this));
    }
  }
</script>

The two important features of the functionality are 1. The show variable in the data() and 2, the prompt-login event we listen to. The show data allows us to toggle the show/hide of the modal. When set to true the modal is shown. When set to false the modal is hidden.

The prompt-login event we listen to toggles the show to either true or false to show the login.

Now to style the form, we will use some of the same styles as we did on the login.blade.php . Add these styles to the <style> tag:

LoginModal styles

<style lang="scss">
  div#login-modal{
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: rgba( 0, 0, 0, .6 );

    div.login-box{
      max-width: 370px;
      min-width: 320px;
      padding: 0 10px;
      background-color: #fff;
      border: 1px solid #ddd;
      -webkit-box-shadow: 0 1px 3px rgba(50,50,50,0.08);
      box-shadow: 0 1px 3px rgba(50,50,50,0.08);
      -webkit-border-radius: 4px;
      border-radius: 4px;
      font-size: 16px;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);

      a{
        display: block;
        margin: auto;
        width: 230px;
        margin-top: 10px;
        margin-bottom: 10px;
      }
    }
  }
</style>

Step 3: Set Up LoginModal.vue

Now that we got our login modal created we need to set it up. First open up your /resources/assets/js/pages/Layout.vue file. We will be importing our login modal here so add to the top of the component script:

Layout component imports

import LoginModal from '../components/global/LoginModal.vue';

Then we will need to register it in our components object:

Layout component registration

components: {
  Navigation,
  AppFooter,
  LoginModal
},

and place it right before the <app-footer></app-footer> component in the template:

Layout component template

<template>
  <div id="app-layout">
    <navigation></navigation>

    <router-view></router-view>

    <login-modal></login-modal>

    <app-footer></app-footer>
  </div>
</template>

We placed it in the Layout.vue template so we can call the login screen from any part of the app.

Now we need to open the Navigation.vue component. In this component, we will display a login link if the user isn't logged in. In the template of this component, adjust the code for the right side navigation:

Navigation component template with conditional rendering

<img class="avatar" v-if="user != '' && userLoadStatus == 2" :src="user.avatar" v-show="userLoadStatus == 2"/>
<span class="logout" v-if="user != '' && userLoadStatus == 2" v-on:click="logout()">Logout</span>
<span class="login" v-if="user == ''" v-on:click="login()">Login</span>

What this does is determine if we should display the avatar if the user is logged in and a logout button or if we should display the login button.

We also need to import the EventBus:

Navigation component EventBus import

import { EventBus } from '../../event-bus.js';

This way when the user clicks the login() link, we can emit the prompt-login method which opens our login modal.

Our login() method looks like:

Navigation component login method

login(){
  EventBus.$emit('prompt-login');
},

We also added a logout method that dispatches an action to our Vuex state to clear any data referring to the user. Our logout method looks like:

Navigation component logout method

logout(){
  this.$store.dispatch('logoutUser');

  window.location = '/logout';
}

When we visit the /logout route our Laravel side of the application will log out the user and redirect them back to the application. We can also listen for the logoutUser event in any Vuex module and clear any data we don't want saved after the user has logged in.

I added the action to /resources/assets/js/modules/users.js:

Vuex logout user action

/*
  Logs out a user and clears the status and user pieces of
  state.
*/
logoutUser( { commit } ){
  commit( 'setUserLoadStatus', 0 );
  commit( 'setUser', {} );
}

Step 4: Adjust Views Based On Authentication

There are a few views we have to adjust based on whether the user is authenticated or not. We need to hide the add button on the cafes homepage, the edit but on the individual cafe page and the like button on the individual cafe page.

First, let's open our Home.vue component. At the top of the page, adjust the template to look like:

Home component template with conditional rendering

<router-link :to="{ name: 'newcafe' }" v-if="user != '' && userLoadStatus == 2"  class="add-cafe-button">+ Add Cafe</router-link>
<a class="add-cafe-text" v-if="user == '' && userLoadStatus == 2" v-on:click="login()">Want to add a cafe? Create a profile and add your favorite cafe!</a>

What this does is hide the add cafe button if the user is not authenticated. Now this doesn't prevent the user from typing in the URL and visiting the page. We will be dealing with Vue Router Navigation Guards in the next tutorial.

Now let's open the Cafe.vue page. I wrapped the Edit link in a container and hid it if the user is not logged in:

Cafe component edit link with conditional rendering

<div class="edit-container" v-if="this.user != '' && this.userLoadStatus == 2">
  <router-link :to="{ name: 'editcafe', params: { id: cafe.id } }">Edit</router-link>
</div>

I also added a toggle for the like button if the user is not logged in. It prompts the user to log in by dispatching the event to login to the Login modal. Our template update should look like:

Cafe component like button with conditional rendering

<toggle-like v-if="user != '' && userLoadStatus == 2"></toggle-like>
<a class="prompt-log-in" v-if="user == '' && userLoadStatus == 2" v-on:click="login()">Did you know you can "like" this cafe and save it to your profile? Just log in!</a>

and we should add the login() method to the methods object on the page:

Cafe component login method

/*
  Defines the methods used by the component.
*/
methods: {
  login(){
    EventBus.$emit('prompt-login');
  }
}

This way we will be able to show the login modal when we click the link to log in.

Conclusion

We now have cleaned up some of the VueJS front end and accounted for both public and private routes, we will add some navigation guards in the next tutorial. This will make sure that users do not navigate to pages they don't have access to when they are not authenticated. This combined with the changes we've made on the Laravel side of things make sure no data is leaked. Even if they were to inject javascript to view a page to add a cafe, we already block the API from adding the cafe if submitted.

Be sure to check out all of the code here: GitHub - serversideup/roastandbrew: Helping the coffee enthusiast find their next cup of coffee. Also, helping aspiring web and mobile app developers build a single page app and converting it to a mobile hybrid. All tutorials can be found at https://serversideup.net

Want to work together?

Professional developers choose Server Side Up to ship quality applications without surrendering control. Explore our tools and resources or work directly with us.

Join our community

We're a community of 3,000+ members help each other level up our development skills.

Platinum Sponsors

Active Discord Members

We help each other through the challenges and share our knowledge when we learn something cool.

Stars on GitHub

Our community is active and growing.

Newsletter Subscribers

We send periodic updates what we're learning and what new tools are available. No spam. No BS.

Sign up for our newsletter

Be the first to know about our latest releases and product updates.

    Privacy first. No spam. No sharing. Just updates.