Using Laravel Passport with AJAX requests

Published Jan 3, 20208 min read 0 comments

Tags: laravel php

Getting Started

When building an app which requires users to log in, Laravel makes authentication easy with its Auth classes. Similarly, the Passport package can be used to facilitate building an API without the hassle of writing an OAuth framework. But Passport also includes a middleware that allows you to consume your API directly from the browser, meaning your API can be used to serve AJAX requests. The benefit to this approach is twofold. You simply only need to maintain a single set of controllers to serve both purposes, while ensuring that results in your web application match the user-facing API.

This example will cover the basics of installing Passport and assumes that you already have a working knowledge of the Laravel framework. It also helps if you are familiar with concepts of an OAuth API.

Code snippets that start with a $ prompt contain sample output and should not be copied verbatim.

Start by installing Passport via composer.

composer require laravel/passport

Adding Migrations

Passport comes with its own database migrations that you can either immediately run or optionally modify by publishing to your own application. If you require custom migrations such as user UUID, follow the next couple steps to make updates. Otherwise, skip directly to the migration.

Customized Migrations

First be sure to disable the default migrations by calling ignoreMigrations() in the register() method of the AppServiceProvider.

File: app/Providers/AppServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Passport\Passport;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        Passport::ignoreMigrations();
    }

    public function boot() {}
}

Next, export Passport's migrations by executing:

php artisan vendor:publish --tag=passport-migrations

and make the necessary changes to the files in the database/migrations/ directory.

Execute Migrations

Finally, run the migrations with the command:

php artisan migrate

Configuring Passport

After the database has been updated, execute the passport:install command which generates the key pair necessary to create access tokens. It also creates the first two clients for consuming the API. Pay attention to the second record as you'll use it to request an access token later.

$ php artisan passport:install
Encryption keys generated successfully.                                         
Personal access client created successfully.                                    
Client ID: 1                                                                    
Client secret: EdKwSvubQyyAqxDJDPrQe8lOZIs0PX89wQskqdrg                         
Password grant client created successfully.                                     
Client ID: 2                                                                    
Client secret: D6hvbmJ87OThJyYqmPQEe1w2VpXH7RqbkaZZCaay                         

If you need to look up client secrets later, you can query the oauth_clients table as well.

By default, the key pair is stored in the storage/ directory.

$ ls storage                                              
app  framework  logs  oauth-private.key  oauth-public.key

There are three files that need to be updated. Start by adding the HasApiTokens trait to your User model.

File: app/User.php

<?php

namespace App;

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

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

    protected $fillable = ['name', 'email', 'password', ];
    protected $hidden = ['password', 'remember_token', ];
    protected $casts = ['email_verified_at' => 'datetime', ];
}

Next, add the Passport routes to the boot() method of the AuthServiceProvider

File: app/Providers/AuthServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [];

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

Lastly, update the auth configuration to use Passport API driver.

File: config/auth.php

<?php

return [
    // configuration options...

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

    // more configuration options...
];

Testing Passport

For this example, we'll create a simple new model for ease-of-use and readability. If you already have data that you'd prefer to use, skip ahead to the next section.

Generate Test Data

Create the model and a corresponding migration.

php artisan make:model -m Preference

This command creates two files, the model app/Preference.php and the migration database/migrations/yyyy_mm_dd_hhmmss_create_preferences_table.php. Copy the following content to each of the files.

The CreatePreferecesTable migration has a couple of columns, id and name, plus the timestamps used in Eloquent models by default.

File: database/migrations/*create_preferences_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreatePreferencesTable extends Migration
{
    public function up()
    {
        Schema::create("preferences", function (Blueprint $table) {
            $table->tinyIncrements("id");
            $table->string("name");
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists("preferences");
    }
}

Keep the model simple allowing the name column to be fillable and suppressing the timestamps columns from its output.

File: app/Preference.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Preference extends Model
{
    protected $fillable = ["name", ];
    protected $hidden = ["created_at", "updated_at", ];
}

Run the migration and populate a few values in the preferences table.

php artisan migrate
$ php artisan tinker                                      
>>> App\Preference::create(["name" => "paper"]);                                
>>> App\Preference::create(["name" => "plastic"]);                              
Define the API route

Create a resource controller by running the following command.

php artisan make:controller PreferencesController --resource

This generates the file app/Http/Controllers/PreferencesController with all of the resource methods used for Laravel's CRUD operations. Modify the file with the following content.

File: app/Http/Controllers/PreferencesController.php

<?php

namespace App\Http\Controllers;

use App\Preference;
use Illuminate\Http\Request;

class PreferencesController extends Controller
{
    public function index()
    {
        return Preference::all();
    }

    // additional resource methods
}

Define the route and you're ready for your first test.

File: routes/api.php

<?php

use Illuminate\Http\Request;

Route::group(["middleware" => "auth:api"], function() {
    Route::resource("preferences", "PreferencesController");
});
Consume the API

Typically, you'll probably a use a tool such as Postman to test RESTful APIs, but for this example we'll simply curl from the command line to ensure we get the proper response. In order to make requests, you will first need an access token. You can requst one using the password grant option and will need to refer to the client secret from the previous section.

$ curl http://localhost/oauth/token \        
-X POST \                                                                       
-d "grant_type=password" \                                                      
-d "client_id=2" \                                                              
-d "client_secret=D6hvbmJ87OThJyYqmPQEe1w2VpXH7RqbkaZZCaay" \                   
-d "username=user@example.com" \                                                
-d "password=password1" \                                                       
-d "scope=*"                                                                    
{"token_type":"Bearer","expires_in":31622400,"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."}

This generates both an access token and a refresh token, but you'll only need the access token for the purpose of this test.

$ curl http://localhost/api/preferences \    
-H "Accept: application/json" \                                                 
-H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9..."
[{"id":1,"name":"paper"},{"id":2,"name":"plastic"}]

If the request is authenticated successfully, you should see the result of the two preferences that you just created in the previous step.

Using AJAX Requests

In addition to the Authorization header, Passport can also authenticate user requests via cookie. The cookie content is simply a JWT token but it's generated automatically without making a request to oauth/token. To enable this feature, add the CreateFreshApiToken class to the web middleware group in app/Http/Kernel.php and ensure that it is called after the EncryptCookies class.

File: app/Http/Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    // Other kernel properties...

    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
            // Additional middleware classes...
        ],

        'api' => [
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    // Additional kernel properties...
}

Create an example route that returns the example view.

File: routes/web.php

<?php

Route::group(["middleware" => "auth"], function() {
    Route::get("example", function() {
        return view("example");
    });
});

Auth::routes();

Now create the corresponding view in resources/views/example.blade.php.

File: resources/views/example.php

<!doctype html>                                                                    
<html lang="en">                                                                             
    <head></head>                                                                  
    <body>                                                                         
        <pre id="result" style="background-color:#eee;padding:5px;"></pre>         
        <script>                                                                   
            fetch("{{ url('api/preferences') }}", {                                     
                headers: {                                                         
                    "X-CSRF-TOKEN": "{{ csrf_token() }}",                          
                    "Content-Type": "application/json"                             
                },                                                                 
            }).then(function(response) {                                           
                return response.json();                                            
            }).then(function(json) {                                               
                var text = JSON.stringify(json, null, "    ");                     
                document.getElementById("result").innerHTML = text;                
            });                                                                    
        </script>                                                                  
    </body>                                                                        
</html>

View the example page in your project directory and you should see the result of the API request.

screenshot

More Articles
Using UUIDs with Laravel
Using UUIDs with Laravel
Jan 15, 2020
Copyright © 2017-2023   Mark Brody | Blog