Make REST API application with Laravel Passport and Vue

Lav Shinde
Jun 6 · 6 min read

This story explains the process of making an API-based authentication system followed with API-based requests to access the data from the Database. Our application uses Vue CLI3 for the frontend and Laravel for the backend. We are building an SPA.

Motivation

The reason for this tech stack is to ensure a perfect “Separation of Concerns” for frontend and backend development. We can achieve such separation correctly by using an API-based authentication. Even if you use the normal method or want to create a multi-page application this tutorial equally holds true. We will take the help of Laravel Passport to handle the authentication on the server side. But the main challenge is to safely store the API tokens generated by the backend in the frontend. I am not an expert on security but as per some sources I have referred to and especially this article, it is clear that using HTTPOnly cookies is one of the best ways to store the token on the client rather than localStorage or cookies which js can read (This makes the process a piece of cake as we can set the Authorization header to Bearer + token in the requests while sending from the client-side). HTTPOnly cookies help us to safeguard token theft from XSS attacks. Note that this application does not use CSRF tokens (I am yet to figure out how to incorporate them into this 😅). Let us begin with the process of setting up Laravel Passport at first.

1. Set up Laravel Passport

Assuming you have already installed Laravel and VueJS and set up an application, do the following to get Passport. For an explanation of the commands, you can read the official docs.

Step 1: Install Laravel Passport Package

composer require laravel/passport

Step 2: Run Migrations

php artisan migrate

Step 3: Generate Keys

php artisan passport:install

This completes the installation of the passport package. You should now see a few tables added to your database and a client in one of the databases, which is required for OAuth, but you don’t need to concern yourselves with that for now.

Now we need to configure our Laravel Backend to use Passport instead of the default token driver for the Authentication of our API routes.

Step 4: Add HasApiTokens to User Model

<?phpnamespace App;use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
}

Step 5: Set up Passport Routes

Call the Passport::routes method within the boot method of your AuthServiceProvider

<?phpnamespace App\Providers;use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}

Step 6: Change Driver

Finally, in your config/auth.php configuration file, set the driver option of the api authentication guard to passport

'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],

Step 7: Add Service Provider

Finally, add the service provider for Passport in your the config/app.php file to theproviders

...'providers' => [
/*
* Laravel Framework Service Providers...
*/
Illuminate\Auth\AuthServiceProvider::class,
...

Laravel\Passport\PassportServiceProvider::class,
],

Note that the ... represents to keep the file as is in those portions.

2. Create Authentication System

Now we will move on to creating an authentication system which will use an API to authenticate the user.

Step 1: Create API routes

Next, we will create API routes for Login, Register, etc. You will add additional routes for your API in the same fileroutes/api.php

<?phpuse Illuminate\Http\Request;Route::post('login', 'AuthController@login');

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

Note that the middleware we are using is auth.api and not auth:api . We will define this middleware later. Note that if you choose to use Local Storage or not HTTPOnly cookies, you may skip the middleware step and use auth:api . Note that in such a case you will need to pass the Authorization: Bearer $token in the header of your request from the frontend. If we use auth.api , we will add this same header from the Middleware instead as the HTTPOnly Cookie passed can only be read by the server.

All the routes which don’t require authentication should be kept outside the group and all the ones which do should be kept inside the group.

Step 2: Create Controller

We will now create a new controller and the two API methods mentioned in the previous step. So let’s create a controller by php artisan make:controller AuthController and put in the code below:

<?phpnamespace App\Http\Controllers;use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Auth;
use Cookie;
class AuthController extends Controller
{
use AuthenticatesUsers; public function __construct()
{
$this->middleware('guest')->except('logout');
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|string|email',
'password' => 'required|string',
]);
$credentials = request(['email', 'password']);
if (Auth::attempt($credentials)) {
$user = Auth::user();
$token = $user->createToken('Personal Access Token')->accessToken;
$cookie = $this->getCookieDetails($token);
return response()
->json([
'logged_in_user' => $user,
'token' => $token,
], 200)
->cookie($cookie['name'], $cookie['value'], $cookie['minutes'], $cookie['path'], $cookie['domain'], $cookie['secure'], $cookie['httponly'], $cookie['samesite']);
} else {
return response()->json(
['error' => 'invalid-credentials'], 422);
}
}
private function getCookieDetails($token)
{
return [
'name' => '_token',
'value' => $token,
'minutes' => 1440,
'path' => null,
'domain' => null,
// 'secure' => true, // for production
'secure' => null, // for localhost
'httponly' => true,
'samesite' => true,
];
}
public function logout(Request $request)
{
$request->user()->token()->revoke();
$cookie = Cookie::forget('_token');
return response()->json([
'message' => 'successful-logout'
])->withCookie($cookie);
}
}

Note that in the Login, we are passing the token in the response as well as in a cookie. We need to only pass the cookie for our method, but I’ve kept the token in the response as well just in case someone wants to store it in localStorage and pass as a header with each request from the frontend.

Middleware to Add Token from Cookie

This part is the real beauty of the entire article. This explains how to use the HTTPOnly cookie to authenticate the user using a Laravel Middleware to add the Authorization header to the request instead of directly passing it to the API backend from the client.

Step 1: Create Middleware

Create a middleware by using the following command php artisan make:middleware AddAuthHeader and then add a function to extract the token from the cookie and add it to the header of the request before forwarding it.

<?phpnamespace App\Http\Middleware;use Closure;class AddAuthHeader
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!$request->bearerToken()) {
if ($request->hasCookie('_token')) {
$token = $request->cookie('_token');
$request->headers->add(['Authorization' => 'Bearer ' . $token]);
}
}
return $next($request);
}
}

Step 2: Add New Middleware in Kernel

Now is the time when we define the middleware auth.api used in the api.php file. So add the following lines in the app\Http\Kernel.php file.

<?phpnamespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
protected $middlewarePriority = [
\App\Http\Middleware\AddAuthHeader::class,
\Illuminate\Auth\Middleware\Authenticate::class,
];


...
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
//\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
// \App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
'auth.api' => [
\App\Http\Middleware\AddAuthHeader::class,
'throttle:60,1',
'bindings',
'auth:api',
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
],

];
...

Note that the ... means to keep the file as is. There are only 13 lines of change which are mentioned in bold. We add the priority to ensure that the new middleware to attach the Authorization header is executed before all further requests. This is really important. And we comment out the VerifyCsrf as we will try to create our entire app using api.php and no routes in web.php . This could be achieved as we used Vue CLI 3 to create a frontend completely independent of our backend. All communications will take place through the API.

Note that you may need to run php artisan config:clear and php artisan cache:clear to make it work.

References

https://medium.com/@sadnub/api-auth-and-graphql-in-laravel-d191e277560d

https://medium.com/modulr/create-api-authentication-with-passport-of-laravel-5-6-1dc2d400a7f

https://www.rdegges.com/2018/please-stop-using-local-storage/

Last Words

Sorry for the long post, but I had a tough time figuring this out and felt a comprehensive post is needed to explain how to do this. Hope this is useful. After this, you may test by calling the login function and then logging out (this will use the auth.api Middleware).

Have fun for building awesome APIs.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade