VueJS Route Security and Authentication
Part 35 of 48 in API Driven Development With Laravel and VueJSIn 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:
{
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:
<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:
<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:
<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:
<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:
import LoginModal from '../components/global/LoginModal.vue';
Then we will need to register it in our components
object:
components: {
Navigation,
AppFooter,
LoginModal
},
and place it right before the <app-footer></app-footer>
component in the 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:
<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
:
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:
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:
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
:
/*
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:
<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:
<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:
<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:
/*
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