How to Secure Laravel APIs with Passport: A Complete OAuth2 Guide

This guide explains how to use Laravel Passport to implement OAuth2 authentication for API endpoints, covering installation, database migrations, token generation, client management, scope definition, route protection, JavaScript integration, event handling, and testing with detailed code examples.

Laravel Tech Community
Laravel Tech Community
Laravel Tech Community
How to Secure Laravel APIs with Passport: A Complete OAuth2 Guide

Introduction

Laravel makes traditional form‑based login easy, but API authentication requires token‑based authorization instead of session state. Laravel Passport provides a full OAuth2 server implementation in minutes, built on the League OAuth2 server maintained by Alex Bilbie.

Note: This document assumes you are already familiar with OAuth2 terminology and concepts.

Installation

Install Passport via Composer: composer require laravel/passport Run the migrations that create the tables for clients and tokens:

php artisan migrate
Note: If you do not want the default migrations, call Passport::ignoreMigrations() in App\Providers\AppServiceProvider::register or publish the migrations with php artisan vendor:publish --tag=passport-migrations .

Generate encryption keys and default clients: php artisan passport:install Add the HasApiTokens trait to your App\User model to provide helper methods for token checks:

<?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;
}

Register Passport routes in AuthServiceProvider::boot:

<?php
namespace App\Providers;

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

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        'App\Model' => 'App\Policies\ModelPolicy',
    ];

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

Set the API guard driver to passport in config/auth.php:

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

Frontend Quick Start

Note: To use Passport's Vue components you need Vue and Bootstrap, but the components can also serve as reference for custom front‑end development.

Publish the Vue components:

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

The components are placed in resources/assets/js/components. Register them in resources/assets/js/app.js:

Vue.component('passport-clients', require('./components/passport/Clients.vue'));
Vue.component('passport-authorized-clients', require('./components/passport/AuthorizedClients.vue'));
Vue.component('passport-personal-access-tokens', require('./components/passport/PersonalAccessTokens.vue'));

Recompile assets with npm run dev and use the components in your Blade templates:

<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>

Deploying Passport

In production, generate encryption keys with:

php artisan passport:keys

Configuration

Token Expiration

By default tokens are valid for one year. You can customize expiration using tokensExpireIn and refreshTokensExpireIn in AuthServiceProvider::boot:

public function boot()
{
    $this->registerPolicies();
    Passport::routes();
    Passport::tokensExpireIn(now()->addDays(15));
    Passport::refreshTokensExpireIn(now()->addDays(30));
}

Issuing Access Tokens

Authorization Code Flow

Redirect the user to /oauth/authorize with required query parameters:

Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'code',
        'scope' => '',
    ]);
    return redirect('http://your-app.com/oauth/authorize?' . $query);
});
Tip: The /oauth/authorize route is automatically defined by Passport::routes() ; you do not need to create it manually.

After the user approves, exchange the authorization code for an access token using Guzzle:

Route::get('/callback', function (Request $request) {
    $http = new \GuzzleHttp\Client;
    $response = $http->post('http://your-app.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
            'redirect_uri' => 'http://example.com/callback',
            'code' => $request->code,
        ],
    ]);
    return json_decode((string) $response->getBody(), true);
});

The JSON response contains access_token, refresh_token, and expires_in (seconds).

Password Grant Tokens

Create a password‑grant client: php artisan passport:client --password Request a token with user credentials:

$http = new \GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => '[email protected]',
        'password' => 'my-password',
        'scope' => '',
    ],
]);
return json_decode((string) $response->getBody(), true);
Tip: By default access tokens are long‑lived; you can adjust their lifetime with the methods shown earlier.

Implicit Grant Tokens

Enable implicit grant in AuthServiceProvider::boot:

Passport::enableImplicitGrant();

Redirect the user with response_type=token:

Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'token',
        'scope' => '',
    ]);
    return redirect('http://your-app.com/oauth/authorize?' . $query);
});

Client Credentials Grant

Add the CheckClientCredentials middleware to app/Http/Kernel.php and apply it to routes:

protected $routeMiddleware = [
    'client' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class,
];

Route::get('/user', function (Request $request) {
    // ...
})->middleware('client');

Obtain a token using client credentials:

$guzzle = new \GuzzleHttp\Client;
$response = $guzzle->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'client_credentials',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'scope' => 'your-scope',
    ],
]);
return json_decode((string) $response->getBody(), true)['access_token'];

Personal Access Tokens

Create a personal‑access client (if not already created by passport:install): php artisan passport:client --personal Generate a personal token for a user:

$user = App\User::find(1);
$token = $user->createToken('Token Name')->accessToken;
// With scopes
$token = $user->createToken('My Token', ['place-orders'])->accessToken;

Personal Access Token JSON API

List available scopes:

axios.get('/oauth/scopes').then(response => console.log(response.data));

List a user's personal tokens:

axios.get('/oauth/personal-access-tokens').then(response => console.log(response.data));

Create a new personal token:

const data = { name: 'Token Name', scopes: [] };
axios.post('/oauth/personal-access-tokens', data)
    .then(response => console.log(response.data.accessToken))
    .catch(error => {/* handle errors */});

Delete a personal token:

axios.delete('/oauth/personal-access-tokens/' + tokenId);

Route Protection

Middleware

Apply the auth:api middleware to routes that require a valid token:

Route::get('/user', function () {
    // ...
})->middleware('auth:api');

Passing the Token

Send the token as a Bearer token in the Authorization header:

$response = $client->request('GET', '/api/user', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer ' . $accessToken,
    ],
]);

Token Scopes

Defining Scopes

Define scopes in AuthServiceProvider::boot using Passport::tokensCan:

use Laravel\Passport\Passport;

Passport::tokensCan([
    'place-orders' => 'Place orders',
    'check-status' => 'Check order status',
]);

Assigning Scopes

When requesting an authorization code, include the desired scopes as a space‑separated list:

Route::get('/redirect', function () {
    $query = http_build_query([
        'client_id' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'code',
        'scope' => 'place-orders check-status',
    ]);
    return redirect('http://your-app.com/oauth/authorize?' . $query);
});

When creating a personal access token, pass the scopes array to createToken:

$token = $user->createToken('My Token', ['place-orders'])->accessToken;

Checking Scopes

Register the scope‑checking middleware in app/Http/Kernel.php:

'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,

Require all listed scopes on a route:

Route::get('/orders', function () {
    // The token has both "check-status" and "place-orders" scopes.
})->middleware('scopes:check-status,place-orders');

Require any of the listed scopes:

Route::get('/orders', function () {
    // The token has either "check-status" or "place-orders".
})->middleware('scope:check-status,place-orders');

Programmatically verify a scope on the authenticated user:

use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    if ($request->user()->tokenCan('place-orders')) {
        // ...
    }
});

Using JavaScript to Access the API

Add the CreateFreshApiToken middleware to the web middleware group in app/Http/Kernel.php so that a laravel_token cookie containing an encrypted JWT is sent with each request:

'web' => [
    // other middleware
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

Axios will automatically include the cookie, allowing you to call protected endpoints without manually attaching the token:

axios.get('/api/user')
    .then(response => console.log(response.data));
Note: Ensure your JavaScript framework sends the X-CSRF-TOKEN and X-Requested-With headers, and that a meta tag with the CSRF token exists in your HTML.

Events

Passport fires events when access or refresh tokens are created. Register listeners in app/Providers/EventServiceProvider.php to react to these events:

protected $listen = [
    'Laravel\Passport\Events\AccessTokenCreated' => [
        'App\Listeners\RevokeOldTokens',
    ],
    'Laravel\Passport\Events\RefreshTokenCreated' => [
        'App\Listeners\PruneOldTokens',
    ],
];

Testing

Use Passport::actingAs in tests to simulate an authenticated user with specific scopes:

public function testServerCreation()
{
    Passport::actingAs(
        factory(User::class)->create(),
        ['create-servers']
    );

    $response = $this->post('/api/create-server');
    $response->assertStatus(200);
}
Passport diagram
Passport diagram
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

PHPOAuth2TokenAPI authenticationLaravelPassport
Laravel Tech Community
Written by

Laravel Tech Community

Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.