Laravel API Authentication with Passport

Secure your API with OAuth2 authentication

Introduction to API Authentication

When building APIs, securing access is crucial. Unlike web applications that use sessions and cookies, APIs typically use token-based authentication. This is where Laravel Passport shines.

Laravel Passport is a full OAuth2 server implementation for your Laravel application. It provides a complete solution for API authentication with minimal setup.

graph TD A[Client Application] -->|Credentials| B[OAuth2 Server] B -->|Access Token| A A -->|Request + Token| C[Protected API] C -->|Response| A style B fill:#f9f,stroke:#333,stroke-width:2px

Think of Passport like a bouncer at an exclusive club. Clients need the right credentials to get a VIP wristband (token), which they then show to access different areas of the club (API endpoints).

OAuth2 Basics

Before diving into Passport, let's understand the OAuth2 protocol it implements:

Real-world analogy: Consider OAuth2 like a hotel key card system. You identify yourself at check-in (authentication) and receive a key card (token) that grants access to specific areas (scopes) for a limited time (expiration).

Installing and Setting Up Passport

Installation

composer require laravel/passport

Database Migration

php artisan migrate

Install Passport

php artisan passport:install

This creates encryption keys and personal access/password grant clients.

User Model Configuration

// In app/Models/User.php
namespace App\Models;

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

class User extends Authenticatable
{
    use HasApiTokens;
    
    // Rest of your User model...
}

Service Provider Configuration

// In app/Providers/AuthServiceProvider.php
namespace App\Providers;

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

class AuthServiceProvider extends ServiceProvider
{
    protected $policies = [
        // Your model policies...
    ];

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

Auth Configuration

// In config/auth.php
'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

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

Passport Grant Types

Laravel Passport supports multiple ways for clients to obtain tokens:

flowchart TB subgraph "Passport Grant Types" A[Password Grant] B[Client Credentials] C[Authorization Code] D[Implicit Grant] E[Personal Access] end

Password Grant

Allows users to obtain tokens using their email and password. Perfect for first-party clients (apps you own).

// Client-side request example (using axios)
axios.post('/oauth/token', {
    grant_type: 'password',
    client_id: '2',
    client_secret: 'client_secret',
    username: 'user@example.com',
    password: 'my-password',
    scope: '',
}).then(response => {
    // Store the tokens
    console.log(response.data.access_token);
});

Personal Access Tokens

Allow users to create long-lived tokens for themselves without the OAuth2 redirection flow.

// In a controller method
return $request->user()->createToken('Token Name')->accessToken;

Protecting Routes with Passport

Basic Route Protection

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

Route Group Protection

Route::middleware('auth:api')->group(function () {
    Route::get('/user', function (Request $request) {
        return $request->user();
    });
    
    Route::get('/products', [ProductController::class, 'index']);
    Route::post('/products', [ProductController::class, 'store']);
    // More protected routes...
});

Accessing the Authenticated User

public function index(Request $request)
{
    $user = $request->user(); // The authenticated user
    
    // Maybe show only the user's products
    return ProductResource::collection(
        Product::where('user_id', $user->id)->get()
    );
}

Token Scopes

Scopes allow you to limit what actions a token can perform, similar to permissions.

Defining Scopes

// In AuthServiceProvider
Passport::tokensCan([
    'view-products' => 'View products',
    'create-products' => 'Create new products',
    'edit-products' => 'Edit existing products',
    'delete-products' => 'Delete products',
]);

Requesting Specific Scopes

// When requesting a token
axios.post('/oauth/token', {
    grant_type: 'password',
    client_id: '2',
    client_secret: 'client_secret',
    username: 'user@example.com',
    password: 'my-password',
    scope: 'view-products create-products',
});

Protecting Routes with Scopes

Route::middleware(['auth:api', 'scope:view-products'])
    ->get('/products', [ProductController::class, 'index']);
    
Route::middleware(['auth:api', 'scope:create-products'])
    ->post('/products', [ProductController::class, 'store']);

Checking Scopes in Controllers

public function update(Request $request, Product $product)
{
    if ($request->user()->tokenCan('edit-products')) {
        // User has permission to edit products
        // Update the product...
        return new ProductResource($product);
    }
    
    return response()->json(['error' => 'Unauthorized'], 403);
}

Using Tokens in Requests

Once a client has a token, they need to include it in requests to protected endpoints.

Bearer Token Authentication

// Using axios
const token = 'eyJ0eXAi...'; // The access token

axios.get('/api/user', {
    headers: {
        'Authorization': `Bearer ${token}`
    }
}).then(response => {
    console.log(response.data);
});

Setting Up a Global Axios Instance

// In a JavaScript file
const api = axios.create({
    baseURL: '/api',
    headers: {
        'Authorization': `Bearer ${localStorage.getItem('access_token')}`
    }
});

// Then use it across your app
api.get('/user').then(response => {
    console.log(response.data);
});

api.post('/products', {
    name: 'New Product',
    price: 99.99
}).then(response => {
    console.log(response.data);
});

Token Management

Passport provides tools for users to manage their own tokens:

Listing Active Tokens

// In a controller
public function tokens(Request $request)
{
    return $request->user()->tokens;
}

Revoking Tokens

// To revoke a specific token
$request->user()->tokens()->where('id', $tokenId)->delete();

// To revoke all tokens
$request->user()->tokens()->delete();

This is useful for implementing "log out from all devices" functionality.

Token Expiration

Set reasonable expiration times for your tokens:

// In AuthServiceProvider
Passport::tokensExpireIn(now()->addHours(2));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::personalAccessTokensExpireIn(now()->addMonths(6));

Testing API Authentication

Passport makes testing authenticated API requests simple:

// In a feature test
use Laravel\Passport\Passport;

public function test_user_can_view_their_products()
{
    $user = User::factory()->create();
    $product = Product::factory()->create(['user_id' => $user->id]);
    
    // Act as the user with specific scopes
    Passport::actingAs($user, ['view-products']);
    
    // Make the request
    $response = $this->getJson('/api/products');
    
    // Assert the response
    $response->assertStatus(200)
             ->assertJsonCount(1, 'data')
             ->assertJsonPath('data.0.id', $product->id);
}

Real-World Authentication Flow Example

User Login and Token Issuance

// LoginController.php
public function login(Request $request)
{
    $credentials = $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);
    
    if (!Auth::attempt($credentials)) {
        return response()->json([
            'message' => 'The provided credentials are incorrect.'
        ], 401);
    }
    
    $user = $request->user();
    $tokenName = 'AuthToken';
    
    // Revoke existing tokens
    $user->tokens()->where('name', $tokenName)->delete();
    
    // Create a new token
    $token = $user->createToken($tokenName, ['view-profile', 'place-orders']);
    
    return response()->json([
        'token' => $token->accessToken,
        'user' => new UserResource($user),
        'expires_at' => now()->addHours(2),
    ]);
}

User Logout

// LogoutController.php
public function logout(Request $request)
{
    // Revoke the token
    $request->user()->token()->revoke();
    
    return response()->json([
        'message' => 'Successfully logged out.'
    ]);
}

Practical Best Practices

Practice Activities

Exercise 1: Basic Passport Setup

Install Passport in a Laravel application and configure it to protect a simple API endpoint.

Exercise 2: Password Grant Implementation

Create a login endpoint that issues tokens using the password grant, then test accessing a protected route.

Exercise 3: Scope Implementation

Define custom scopes for your API and modify routes to check for these scopes.

Exercise 4: Token Management

Create endpoints that allow users to view and revoke their own tokens.

Summary

Laravel Passport provides a complete OAuth2 implementation for your API, allowing you to:

By implementing Passport, you can create secure, robust APIs that safely expose your application's functionality to authenticated clients.