API Authentication in Laravel-Vue SPA using Jwt-auth

Olu Udeh
codeburst
Published in
9 min readJan 19, 2018

--

Introduction: Laravel is the fastest growing PHP framework and is used by thousands of developers around the globe to rapidly build robust web applications. Vue on the other hand is a lightweight front-end framework that can be used to build sophisticated Single-Page Applications.

Combining the powers of these great frameworks, developers can build very powerful applications with much ease. The good news is that integrating vue into laravel is easy as laravel comes with in-built support for vue.

This tutorial will walk you through the process of providing authentication for your vue Single Page Application (SPA) to be able to access API endpoints in laravel requiring authentication.

Resources: The following resources were used in this tutorial.

  • NodeJS 8.9.1
  • Laravel 5.5
  • jwt-auth 0.5.12
  • NPM 5.6.0
  • VueJS 2.5.7
  • Vue-router
  • Vue-axios
  • @websanova/vue-auth

Setup

  • Create a laravel project by running the following command on your terminal.
composer create-project laravel/laravel lara-vue-auth –prefer-dist
  • Move into the project directory by running the following command on your terminal.
cd lara-vue-auth
  • Next you will need to install javascript dependencies by running the following command on your terminal.
npm install
  • Next supply your database credentials in your .env file and create a database for use with your application (If it does not exist already).
  • Next run database migration to create the users table and the password_resets table by running the following command on your terminal.
php artisan migrate
  • Install some vue libraries that we will need. Run the following command on your terminal.
npm install --save-dev vue-axios vue-router vue-loader vue-template-compiler
  • Create a file named App.vue in resources/assets/js and put the following content in the file.
<template>
<div class="panel panel-default">
<div class="panel-heading">
<nav>
<ul class="list-inline">
<li>
<router-link :to="{ name: 'home' }">Home</router-link>
</li>
<li class="pull-right">
<router-link :to="{ name: 'login' }">Login</router-link>
</li> <li class="pull-right">
<router-link :to="{ name: 'register' }">Register</router-link>
</li>
</ul>
</nav>
</div>
<div class="panel-body">
<router-view></router-view>
</div>
</div>
</template>
  • Create another file named Home.vue but now in resources/assets/js/components and put the following code in it.
<template>
<h1>Laravel 5 Vue SPA Authentication</h1>
</template>
  • Then replace the content of the resouces/assets/js/app.js file with the code below.
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import Home from './components/Home.vue';

Vue.use(VueRouter);

const router = new VueRouter({
routes: [
{
path: '/',
name: 'home',
component: Home
},
]
});

new Vue({
el: '#app',
router: router,
render: app => app(App)
});
  • Next replace the content of the resources/views/welcome.blade.php template file with the code below.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Laravel</title>

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">

</head>
<body>
<div class="container">
<div id="app"></div>
</div>
<script src="/js/app.js"></script>
</body>
</html>

Now run the following command on your terminal.

npm run watch

Then

php artisan serve

Go to your browser and visit http://localhost:8000. If you everything went well you should be able to see your home page.

Creating Vue Components

Let us create the vue components we will be needing.

Create a file named Register.vue in the resources/assets/js/components directory and put the following code in it.

<template>
<div>
<div class="alert alert-danger" v-if="error && !success">
<p>There was an error, unable to complete registration.</p>
</div>
<div class="alert alert-success" v-if="success">
<p>Registration completed. You can now <router-link :to="{name:'login'}">sign in.</router-link></p>
</div>
<form autocomplete="off" @submit.prevent="register" v-if="!success" method="post"> <div class="form-group" v-bind:class="{ 'has-error': error && errors.name }">
<label for="name">Name</label>
<input type="text" id="name" class="form-control" v-model="name" required>
<span class="help-block" v-if="error && errors.name">{{ errors.name }}</span>
</div>
<div class="form-group" v-bind:class="{ 'has-error': error && errors.email }">
<label for="email">E-mail</label>
<input type="email" id="email" class="form-control" placeholder="user@example.com" v-model="email" required>
<span class="help-block" v-if="error && errors.email">{{ errors.email }}</span>
</div>
<div class="form-group" v-bind:class="{ 'has-error': error && errors.password }">
<label for="password">Password</label>
<input type="password" id="password" class="form-control" v-model="password" required>
<span class="help-block" v-if="error && errors.password">{{ errors.password }}</span>
</div>
<button type="submit" class="btn btn-default">Submit</button> </form>
</div>
</template>

Create another file named Login.vue in the same directory and put the following code in it.

<template>
<div>
<div class="alert alert-danger" v-if="error">
<p>There was an error, unable to sign in with those credentials.</p>
</div>
<form autocomplete="off" @submit.prevent="login" method="post">
<div class="form-group">
<label for="email">E-mail</label>
<input type="email" id="email" class="form-control" placeholder="user@example.com" v-model="email" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" class="form-control" v-model="password" required>
</div>
<button type="submit" class="btn btn-default">Sign in</button>
</form>
</div>
</template>

Also create another file named Dashboard.vue in the same directory and put the following code in it.

<template>
<h1>Laravel 5 – Our Cool Dashboard</h1>
</template>

Then replace the content of the resources/assets/js/app.js file with the code below.

import Vue from 'vue';import VueRouter from 'vue-router';import axios from 'axios';import VueAxios from 'vue-axios';import App from './App.vue';import Dashboard from './components/Dashboard.vue';import Home from './components/Home.vue';import Register from './components/Register.vue';import Login from './components/Login.vue';Vue.use(VueRouter);Vue.use(VueAxios, axios);axios.defaults.baseURL = 'http://localhost:8000/api';const router = new VueRouter({    routes: [{        path: '/',        name: 'home',        component: Home    },{        path: '/register',        name: 'register',        component: Register    },{        path: '/login',        name: 'login',        component: Login    }]});

@websanova/vue-auth

This is the library responsible for handling authentication on the client side. It injects an $auth object which provides a handful of helper functions such as register() which handles user registration, login() which handles user login, user() which provides access to the current user data, logout() with handles logout, and a couple of other functions.

Install @websanova/vue-auth.

npm install @websanova/vue-auth

Modify app.js to look like the code below.

import Vue from 'vue';import VueRouter from 'vue-router';import axios from 'axios';import VueAxios from 'vue-axios';import App from './App.vue';import Dashboard from './components/Dashboard.vue';import Home from './components/Home.vue';import Register from './components/Register.vue';import Login from './components/Login.vue';Vue.use(VueRouter);Vue.use(VueAxios, axios);axios.defaults.baseURL = 'http://localhost:8000/api';const router = new VueRouter({    routes: [{        path: '/',        name: 'home',        component: Home    },{        path: '/register',        name: 'register',        component: Register,        meta: {            auth: false
}
},{ path: '/login', name: 'login', component: Login, meta: { auth: false } },{ path: '/dashboard', name: 'dashboard', component: Dashboard, meta: { auth: true } }]});Vue.router = routerVue.use(require('@websanova/vue-auth'), { auth: require('@websanova/vue-auth/drivers/auth/bearer.js'), http: require('@websanova/vue-auth/drivers/http/axios.1.x.js'), router: require('@websanova/vue-auth/drivers/router/vue-router.2.x.js'),});App.router = Vue.routernew Vue(App).$mount('#app');

In the code above we have included the library we just installed and given the library some configurations to work with.

auth: require(‘@websanova/vue-auth/drivers/auth/bearer.js’)

The line above configures vue-auth to use the bearer driver which basically adds the authentication token to the our request header during requests and reads and parses the token from our server responses.

http: require(‘@websanova/vue-auth/drivers/http/axios.1.x.js’)

The option above configures vue-auth to use the axios http driver, since we are using axios for our http requests.

router: require(‘@websanova/vue-auth/drivers/router/vue-router.2.x.js’)

The line above configures vue-auth to use the driver for vue-router since that is what we are using in our application.

We also added a meta option to our routes.

 ...
meta: {
auth: true
}
...

The auth property specifies whether authorization is needed to access the route. So, we specified that authorization is needed to access our dashboard; and also specified that logged in users should not be able to access login and register routes by setting the auth property to false.

Check out @websanova/vue-auth to learn more about this library.
Now run

npm run watch 

and try to access the dashboard from your browser. This should redirect you to the login page.

Jwt-auth

Moving forward, we would need to install the jwt-auth library in laravel. This is the library that handles the authentication over our api.

To install run the following command on your terminal.

composer require tymon/jwt-auth

Then add the service JWTAuthServceProvider to the providers array and the JWTAuth facade to the aliases array in config/app.php

...'providers' => [
...
Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
]
...
'aliases' => [
...
'JWTAuth' => Tymon\JWTAuth\Facades\JWTAuth::class,
]

Publish the configuration.

php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"

Generate a key in the published configuration

php artisan jwt:generate

Note: if this error gives you an error checkout this link to fix it.

Edit app/Http/Kernel.php adding jwt.auth and jwt.refresh to the application’s route middleware array.

protected $routeMiddleware = [
...
'jwt.auth' => \Tymon\JWTAuth\Middleware\GetUserFromToken::class,
'jwt.refresh' => \Tymon\JWTAuth\Middleware\RefreshToken::class,
];

Registration

Before we dive into that, let us create a controller and add the required route to our routes/api.php.

First, let us create a controller for authentication.

php artisan make:controller AuthController

Add the route.

Route::post(‘auth/register’, ‘AuthController@register’);

Let us also create a FormRequest to handle validation for every registration request.

php artisan make:request RegisterFormRequest

Let us edit our RegisterFormRequest class to reflect the code below.

...
class RegisterFormRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|string|unique:users',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:6|max:10',
];
}
}

Let us now create the method that will handle user registration in our AuthController.

public function register(RegisterFormRequest $request)
{
$user = new User;
$user->email = $request->email;
$user->name = $request->name;
$user->password = bcrypt($request->password);
$user->save();
return response([
'status' => 'success',
'data' => $user
], 200);
}

The RegisterFormRequest class makes sure each request complies with the rules we have set in it’s rules() method. Then the register() method collects user inputs from the $request variable, encrypts the user password and persists the user data into our database.

Now let us go over to vue and connect the dots. Go to your Register.vue file and append the code below to the end of the file.

<script> 
export default {
data(){
return {
name: '',
email: '',
password: '',
error: false,
errors: {},
success: false
};
},
methods: {
register(){
var app = this
this.$auth.register({
data: {
name: app.name,
email: app.email,
password: app.password
},
success: function () {
app.success = true
},
error: function (resp) {
app.error = true;
app.errors = resp.response.data.errors;
},
redirect: null
});
}
}
}
</script>

Now let us try to register a user. Run

npm run watch

Head over to your browser, fill the form and click on register. If everything went well, you should be able to register a user.

Login

Go back to the AuthController and add the login() method.

public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if ( ! $token = JWTAuth::attempt($credentials)) {
return response([
'status' => 'error',
'error' => 'invalid.credentials',
'msg' => 'Invalid Credentials.'
], 400);
}
return response([
'status' => 'success'
])
->header('Authorization', $token);
}

Also add the user() and refresh() methods.

public function user(Request $request)
{
$user = User::find(Auth::user()->id);
return response([
'status' => 'success',
'data' => $user
]);
}
public function refresh()
{
return response([
'status' => 'success'
]);
}

The user() is used to fetch user data while the refresh() method is used to refresh that the current token while checking if it is still valid.

Append the code below to your routes/api.php file.

Route::post('auth/login', 'AuthController@login');
Route::group(['middleware' => 'jwt.auth'], function(){
Route::get('auth/user', 'AuthController@user');
});Route::group(['middleware' => 'jwt.refresh'], function(){
Route::get('auth/refresh', 'AuthController@refresh');
});

Let’s connect with vue. Go to your Login.vue and append the following code to the file.

<script>
export default {
data(){
return {
email: null,
password: null,
error: false
}
},
methods: {
login(){
var app = this
this.$auth.login({
params: {
email: app.email,
password: app.password
},
success: function () {},
error: function () {},
rememberMe: true,
redirect: '/dashboard',
fetchUser: true,
});
},
}
}
</script>

That should do it. At this point, you should be able to login after running

npm run watch

Logout

Back to our AuthController, let’s add a logout() method.

public function logout()
{
JWTAuth::invalidate();
return response([
'status' => 'success',
'msg' => 'Logged out Successfully.'
], 200);
}

This method makes sure that the user is logged out of our application back-end thereby invalidating the authentication token which is also cleared from the client side.

Let’s also add the route to our routes/api.php.

Route::group(['middleware' => 'jwt.auth'], function(){   ...
Route::post('auth/logout', 'AuthController@logout');
});

Let’s modify our App.vue.

<template>
<div class="panel panel-default">
<div class="panel-heading">
<nav>
<ul class="list-inline">
<li>
<router-link :to="{ name: 'home' }">Home</router-link>
</li>
<li v-if="!$auth.check()" class="pull-right">
<router-link :to="{ name: 'login' }">Login</router-link>
</li>
<li v-if="!$auth.check()" class="pull-right">
<router-link :to="{ name: 'register' }">Register</router-link>
</li>
<li v-if="$auth.check()" class="pull-right">
<a href="#" @click.prevent="$auth.logout()">Logout</a>
</li>
</ul>
</nav>
</div>
<div class="panel-body">
<router-view></router-view>
</div>
</div>
</template>

The $auth.check() is used if the user is logged in and the $auth.logout() logs the user out.

Run

npm run watch 

and give it a try. Everything should work just fine.

Conclusion

In this tutorial we have demonstrated how to authenticate api requests for a vue based single paged laravel application.
The full source code for this tutorial can be found on github.

--

--