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.
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 migrateNote: 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-componentsThe 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:keysConfiguration
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);
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Laravel Tech Community
Specializing in Laravel development, we continuously publish fresh content and grow alongside the elegant, stable Laravel framework.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
