Community Post

Getting started with Laravel Passport

Neo Ighodaro

When developing modern software applications, most times you will find yourself more and more needing an API that would serve as an interface for your application and database. In addition to creating the API, you would indeed want to keep it secure to avoid people who do not have access to your API from being able to access it. This is where Laravel Passport comes in.

In this tutorial, I will show you how to set up a Laravel project (an API) to use Passport as it's authentication driver. This tutorial assumes you are familiar with Laravel and have a basic understanding of OAuth.

Why use Laravel Passport?

If you have an API and you need authentication to secure the API then you need to use Laravel Passport to make sure this is covered. Also, if you have an API that you want others to consume, then Laravel Passport will provide you with everything you need to provide your consumers secure access to your API.

Setting up our demo application

For this demo we are going to have two applications. The client (or consumer) and the API. The client will be a sample application attempting to connect securely to our API to get some secure data from the API. The API will be, well, our make-believe API.

Setting up the API

For the sake of this tutorial let us assume we were creating a todo list. Let's create a new Laravel application, I prefer using Laravel installer, you are free to use any method that is most convienient for you.

$ laravel new todos

Now that we are done, we have to create some migrations and seed the database tables with some sample data.

$ cd todos
$ php artisan make:model Todo --migration --controller

The artisan command below will do som many wonderful things for us. It will first create a Todo model, create a migration for the todos table, and finally generate a TodoController file for us all in one line. Nifty right?

Now let us quickly edit the migration created for us. Open the migration file just created for you in the database directory and update the migration block.

Schema::create('todos', function (Blueprint $table) {
    $table->increments('id');
    $table->unsignedInteger('user_id');
    $table->string('task');
    $table->boolean('done');
    $table->timestamps();
});

Now, let us add a new route to the routes/api.php file. We are adding it here because we intend this to be an api accessible endpoint.

// routes/api.php
Route::get('/todos', 'TodoController');

Now lets update the TodoController to answer to our /todos endpoint. The endpoint will basically return all the todos on our system.

// app/Http/Controllers/TodoController.php
<?php

namespace App\Http\Controllers;

use App\Todo;

class TodoController extends Controller
{
    public function __invoke()
    {
        return Todo::all();
    }
}

Protip: If your Controller only has one method and it responds to one route, you can use the __invoke method as the only method. This way, you do not have to specify the @method when defining routes.

Now that we are done with the controller, we can create a seeder. This will fill up our database with some sample data.

$ php artisan make:seed UsersTableSeeder
$ php artisan make:seed TodosTableSeeder

Now, we have the seeder files. Let us edit the seeder files so we can add the logic for the data we want to create. We will use Laravel's awesome model factories to to generate the sample data.

// database/seeds/UsersTableSeeder.php
factory(App\User::class)->create(['email' => 'johndoe@scotch.io']);

// database/seeds/TodosTableSeeder.php
factory(App\Todo::class, 10)->create(['user_id' => 1]);

// database/seeds/DatabaseSeeder.php
$this->call(UsersTableSeeder::class);
$this->call(TodosTableSeeder::class);

And finally, the last piece of the puzzle is creating the model factory entry. In your database/factories/ModelFactory.php add the following block:

$factory->define(App\Todo::class, function (Faker\Generator $faker) {
    return [
        'task' => $faker->sentence,
        'done' => rand(0,1),
    ];
});

Now we need to make sure we have a database set up and the database details have been entered in our .env file. After which we can then run the command:

$ php artisan migrate --seed

Now, visiting your endpoints URL http://todos.dev/api/todos should return a JSON object filled with sample todo items. Great.

Protip: Laravel valet is a Laravel development environment for Mac minimalists and it is easy to set up.

Setting up the consumer

We will keep it simple, just create a new directory somewhere in your development machine.

$ mkdir -p todoconsumer
$ cd todoconsumer
$ echo '<?php require "vendor/autoload.php";' > index.php
$ echo '{}' > composer.json
$ composer require guzzlehttp/guzzle

Make sure the consumer is accessible via HTTP (Laravel valet can also help with this). That's all, we will get back to this consumer app in a bit.

Setting up Laravel Passport for Authentication

Now that we have our API working, let us make it secure using Laravel Passport. In your todo API directory run the command to install Laravel Passport.

$ composer require laravel/passport

Next, register the Passport service provider in the providers array of your config/app.php configuration file:

Laravel\Passport\PassportServiceProvider::class,

Now, you can run the following commands to fully install Laravel Passport to your application.

$ php artisan migrate
$ php artisan passport:install

You will notice, the passport:install artisan command will return something similar to

Encryption keys generated successfully.
Personal access client created successfully.
Client ID: 1
Client Secret: OUA4IhQj1t3kDRuWZ6N7DQb9h1N3ccXpQw9HS2iz
Password grant client created successfully.
Client ID: 2
Client Secret: oGhkm0EPSjqxJBMkaWNZ6lIuuZoby4ev787yW6cO

Passport has automatically installed two client applications for us, complete with ID and secret. Note down the client secret for client ID 1, we will need this later.

Next, you will need to add the Laravel\Passport\HasApiTokens trait to your App\User model.

<?php

namespace App;

use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;
}

Next, we need to register the Laravel passport routes. This is what provides us with authorization URLs for our clients to generate access tokens and authorize their requests.

// app/Providers/AuthServiceProvider.php
public function boot()
{
    $this->registerPolicies();

    Passport::routes();
}

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

'guards' => [
    // ...

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

That's all. We have installed Laravel passport completely. Now, lets move on to actually using Passport.

Protecting your endpoints with Laravel Passport

At this point, our endpoint is still returning the entire list of todos whether we are authorized to see them or not, let's fix that.

In the routes file, add the middleware auth:api to the endpoints you want to protect. In our case, just the one endpoint.

// routes/api.php
Route::get('todos', 'TodoController')->middleware('auth:api');

Now when we hit the endpoint using Postman, we will get an Unauthenticated error.

That is all. We are done with Laravel Passport. You can actually stop reading or continue to see how a sample client would consume this API.

Protip: When visiting the endpoint using Postman, set the Header Accept: application/json or Laravel Passport would never know it's an API client and thus redirect to a /login page for the web.

Consuming the API from another application

Now that we have successfully protected our precious todo list, imagine if we wanted to access this list from a web application we own, how would we go about this?

Let us return to the our consumer application. We are going to use the passwprd grant type to get an access token and use that to make a request to the protected endpoint. Edit the index.php file in the consumer:

<?php

require "vendor/autoload.php";

$client = new GuzzleHttp\Client;

try {
    $response = $client->post('http://todos.dev/oauth/token', [
        'form_params' => [
            'client_id' => 2,
            // The secret generated when you ran: php artisan passport:install
            'client_secret' => 'fx5I3bspHpnuqfHFtvdQuppAzdXC7nJclMi2ESXj',
            'grant_type' => 'password',
            'username' => 'johndoe@scotch.io',
            'password' => 'secret',
            'scope' => '*',
        ]
    ]);

    // You'd typically save this payload in the session
    $auth = json_decode( (string) $response->getBody() );

    $response = $client->get('http://todos.dev/api/todos', [
        'headers' => [
            'Authorization' => 'Bearer '.$auth->access_token,
        ]
    ]);

    $todos = json_decode( (string) $response->getBody() );

    $todoList = "";
    foreach ($todos as $todo) {
        $todoList .= "<li>{$todo->task}".($todo->done ? '✅' : '')."</li>";
    }

    echo "<ul>{$todoList}</ul>";

} catch (GuzzleHttp\Exception\BadResponseException $e) {
    echo "Unable to retrieve access token.";
}

So now, if we visit the url to our consumer, we should see the list of available todos in glorious unordered list.

Conclusion

Laravel Passport is a very powerful tool and this does not even scratch the surface of what it is capable of doing. There are a myriad of features that make Laravel passport the best way to implement OAuth on your application.

You can get the source code to the demo project here. If you encounter any problem while following along with this tutorial, let me know via the comment section below.

Neo Ighodaro

4 posts

CTO at https://hotels.ng | Founder at https://creativitykills.co, Designing for Web and Mobile for over 12 years.