Laravel 5.7 - API authentification with Laravel Passport

Oct 30, 2018 (3 min read)

In this tutorial we will develop a full API authentication system which can be used by any application which is able to perform requests (React, Vue or Angular for instance).

We will use Laravel 5.7 and its Passport package: https://laravel.com/docs/5.7/passport

Let’s start!

Install Laravel 5.7 via composer:

composer create-project --prefer-dist laravel/laravel api-authentification

Configure the connection to the database in our .env file:

DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=api-authentification
DB_USERNAME=root
DB_PASSWORD=

Install the basic authentication system integrated in Laravel and Laravel Passport:

composer require laravel/passport
php artisan make:auth
php artisan passport:install
php artisan migrate

Add the Laravel\Passport\HasApiTokens trait to our App\User model and the Passport::routesmethod within the boot method of our app/AuthServiceProvider like that:

public function boot() {
    $this->registerPolicies();
    Passport::routes();
}

And finally set the driver option of the api authentication guard to passport like that:

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

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],

Laravel Passport is now installed and configured, now we will create a controller to:

  • Create an account
  • Log in
  • Sign out

Let’s go! Create your new controller:

php artisan make:controller Api/AuthController

In this new controller we will create 3 functions: register, login and logout.

Add at the top of the file:

use App\User;
use Illuminate\Support\Facades\Validator;

Create the 3 functions:

Register function:

public function register (Request $request) {

    $validator = Validator::make($request->all(), [
        'name' => 'required|string|max:255',
        'email' => 'required|string|email|max:255|unique:users',
        'password' => 'required|string|min:6|confirmed',
    ]);

    if ($validator->fails())
    {
        return response(['errors'=>$validator->errors()->all()], 422);
    }

    $request['password']=Hash::make($request['password']);
    $user = User::create($request->toArray());

    $token = $user->createToken('Laravel Password Grant Client')->accessToken;
    $response = ['token' => $token];

    return response($response, 200);

}

Login function:

public function login (Request $request) {

    $user = User::where('email', $request->email)->first();

    if ($user) {

        if (Hash::check($request->password, $user->password)) {
            $token = $user->createToken('Laravel Password Grant Client')->accessToken;
            $response = ['token' => $token];
            return response($response, 200);
        } else {
            $response = "Password missmatch";
            return response($response, 422);
        }

    } else {
        $response = 'User does not exist';
        return response($response, 422);
    }

}

Logout function:

public function logout (Request $request) {

    $token = $request->user()->token();
    $token->revoke();

    $response = 'You have been succesfully logged out!';
    return response($response, 200);

}

Tadam! It’s done.

Before testing, and because we use Laravel only as an API provider, we are going to force all routes to return json. We will use Alex Lichter’s method.

Create a middleware:

php artisan make:middleware ForceJsonResponse
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class ForceJsonResponse
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle(Request $request, Closure $next)
    {
        $request->headers->set('Accept', 'application/json');

        return $next($request);
    }
}

Add to the $routeMiddleware of the app/Http/Kernel.php file:

'json.response' => \App\Http\Middleware\ForceJsonResponse::class,

We can now add our routes in routes/api.php. The register and login routes are public while the logout route will only be accessible when the user is logged in.

So we have:

Route::group(['middleware' => ['json.response']], function () {

    Route::middleware('auth:api')->get('/user', function (Request $request) {
        return $request->user();
    });

    // public routes
    Route::post('/login', 'Api\AuthController@login')->name('login.api');
    Route::post('/register', 'Api\AuthController@register')->name('register.api');

    // private routes
    Route::middleware('auth:api')->group(function () {
        Route::get('/logout', 'Api\AuthController@logout')->name('logout');
    });

});

We will now test that everything is ok with Postman (you can use any other tool to simulate http requests).

We can now check that the route /api/user is not accessible without authentication:

1 Ca Jo39 SN h9 MEM b1 ZA 7 0 Xg 075ffd9576

We can not access /api/user without being authenticated

Creating an account:

1 Fvndv49 ULT 5 Qd Bwav8 UMZ w 3bce7c1151

To create an account, make a POST request to /api/register

The register route returns a token (because it automatically logs you) but we will regenerate it with the login route to check that works well.

We log in with the account we just created:

1 Iah NF h Tv WC wz Av F4o LG y A 4df327ce10

To log in, make a POST request to /api/login

We copy the token returned to us (this token proves that we are connected).

We go back to the window where we tested the route /api/user by adding this time our token. For that, go in the tab "Authorization" and select "Bearer Token", paste the token in the input "Token":

1 d MDUHK h OA cn LN 0 Hx2 Rm S5 A c77e89f0c5

Yeah it’s a longggg token

Just press “Send”, and…

1 5ax3qi2y IH w3d Zld Yldx Sg 15d626cbbf

Tadam! This time, informations about your account are returned.

If you want to go further you can find a lot of information on the features of Laravel Passport here: https://laravel.com/docs/5.7/passport