Security & Authentication

Securing API Authentication in Laravel 12 with OAuth2 and Passport

Complete guide to implementing OAuth2 authentication in Laravel 12 using Laravel Passport. Learn token management, scopes, and API security best practices.

Muhammad Waqas

Muhammad Waqas

CEO at CentoSquare

|
03-Nov-2025
10 min read
Securing API Authentication in Laravel 12 with OAuth2 and Passport

Introduction

In modern web development, APIs are the backbone of mobile applications, single-page applications (SPAs), and third-party integrations. Securing these APIs with proper authentication is critical to protecting user data and preventing unauthorized access.

OAuth2 is the industry-standard protocol for API authentication, and Laravel Passport provides a full OAuth2 server implementation for Laravel applications. Whether you're building a mobile app backend, a microservices architecture, or exposing APIs to third-party developers, Passport simplifies token-based authentication while maintaining security.

In this comprehensive guide, we'll implement OAuth2 authentication in Laravel 12 using Passport, covering personal access tokens, password grant tokens, authorization codes, scopes, and production security practices.

By the end of this tutorial, you'll have a production-ready API authentication system that follows OAuth2 standards and industry best practices.


Why OAuth2 and Laravel Passport?

The OAuth2 Advantage

OAuth2 solves several authentication challenges:

  • Token-Based Authentication – No sessions, perfect for stateless APIs
  • Granular Permissions – Control access with scopes
  • Third-Party Access – Allow external applications to access your API
  • Multiple Clients – Support web, mobile, and desktop applications
  • Refresh Tokens – Long-lived authentication without storing passwords

Why Choose Laravel Passport?

Laravel Passport offers:

  • Full OAuth2 Server – Implements all OAuth2 grant types
  • Simple Setup – Artisan commands handle migrations and keys
  • Vue Components – Built-in UI for client management
  • Personal Access Tokens – Simple tokens for testing and mobile apps
  • Middleware Protection – Easy route authentication
  • Token Scopes – Fine-grained permission control
  • Laravel 12 Compatible – Fully tested with the latest Laravel version

Installation and Setup

Step 1: Install Laravel Passport

Install Passport via Composer:

composer require laravel/passport

Step 2: Run Migrations

Passport includes migrations for OAuth2 tables:

php artisan migrate

This creates tables for clients, tokens, and authorization codes.

Step 3: Install Passport

Run the installation command to generate encryption keys:

php artisan passport:install

This generates:

  • Encryption keys for token signing
  • Personal access client
  • Password grant client

Important: Save the client IDs and secrets shown in the output. You'll need them for password grant authentication.

Step 4: Configure the User Model

Update your App\Models\User model to use the HasApiTokens trait:

namespace App\Models;

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

class User extends Authenticatable
{
    use HasApiTokens;

    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    protected $hidden = [
        'password',
        'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];
}

Step 5: Configure Authentication Guard

Update config/auth.php to use the Passport token driver:

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

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

Step 6: Register Passport Routes

In your App\Providers\AppServiceProvider, add Passport routes:

namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Passport::enablePasswordGrant();
        Passport::tokensExpireIn(now()->addDays(15));
        Passport::refreshTokensExpireIn(now()->addDays(30));
        Passport::personalAccessTokensExpireIn(now()->addMonths(6));
    }
}

Personal Access Tokens

Personal access tokens are the simplest way to authenticate API requests, perfect for mobile apps and testing.

Creating Personal Access Tokens

Create a controller for token management:

php artisan make:controller Api/AuthController
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    /**
     * Register a new user
     */
    public function register(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed',
        ]);

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password),
        ]);

        $token = $user->createToken('auth_token')->accessToken;

        return response()->json([
            'access_token' => $token,
            'token_type' => 'Bearer',
            'user' => $user,
        ], 201);
    }

    /**
     * Login user and create token
     */
    public function login(Request $request)
    {
        $request->validate([
            'email' => 'required|email',
            'password' => 'required',
        ]);

        $user = User::where('email', $request->email)->first();

        if (!$user || !Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['The provided credentials are incorrect.'],
            ]);
        }

        // Create token with scopes
        $token = $user->createToken('auth_token', ['read', 'write'])->accessToken;

        return response()->json([
            'access_token' => $token,
            'token_type' => 'Bearer',
            'user' => $user,
        ]);
    }

    /**
     * Get authenticated user
     */
    public function user(Request $request)
    {
        return response()->json($request->user());
    }

    /**
     * Logout user (revoke token)
     */
    public function logout(Request $request)
    {
        $request->user()->token()->revoke();

        return response()->json([
            'message' => 'Successfully logged out',
        ]);
    }
}

API Routes for Personal Access Tokens

Add routes in routes/api.php:

use App\Http\Controllers\Api\AuthController;

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:api')->group(function () {
    Route::get('/user', [AuthController::class, 'user']);
    Route::post('/logout', [AuthController::class, 'logout']);
});

Testing Personal Access Tokens

# Register a new user
curl -X POST http://your-app.test/api/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "email": "john@example.com",
    "password": "password123",
    "password_confirmation": "password123"
  }'

# Login and get token
curl -X POST http://your-app.test/api/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "john@example.com",
    "password": "password123"
  }'

# Use token to access protected route
curl -X GET http://your-app.test/api/user \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Password Grant Tokens

Password grant is ideal for first-party applications where you trust the client with user credentials.

Configure Password Grant

Add route for token exchange:

Route::post('/oauth/token', function (Request $request) {
    $request->validate([
        'grant_type' => 'required|in:password,refresh_token',
        'client_id' => 'required',
        'client_secret' => 'required',
        'username' => 'required_if:grant_type,password',
        'password' => 'required_if:grant_type,password',
        'refresh_token' => 'required_if:grant_type,refresh_token',
        'scope' => 'nullable',
    ]);

    $http = new \GuzzleHttp\Client;

    try {
        $response = $http->post(config('app.url') . '/oauth/token', [
            'form_params' => [
                'grant_type' => $request->grant_type,
                'client_id' => $request->client_id,
                'client_secret' => $request->client_secret,
                'username' => $request->username,
                'password' => $request->password,
                'refresh_token' => $request->refresh_token,
                'scope' => $request->scope ?? '',
            ],
        ]);

        return json_decode((string) $response->getBody(), true);
    } catch (\GuzzleHttp\Exception\BadResponseException $e) {
        return response()->json([
            'error' => 'invalid_credentials',
            'message' => 'The provided credentials are incorrect.',
        ], 401);
    }
});

Request Password Grant Token

curl -X POST http://your-app.test/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "password",
    "client_id": "your-client-id",
    "client_secret": "your-client-secret",
    "username": "john@example.com",
    "password": "password123",
    "scope": "*"
  }'

Refresh Token Flow

curl -X POST http://your-app.test/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "refresh_token": "your-refresh-token",
    "client_id": "your-client-id",
    "client_secret": "your-client-secret",
    "scope": "*"
  }'

Token Scopes

Scopes allow fine-grained permission control for API access.

Define Scopes

In App\Providers\AppServiceProvider:

use Laravel\Passport\Passport;

public function boot(): void
{
    Passport::tokensCan([
        'read-posts' => 'Read posts',
        'create-posts' => 'Create posts',
        'update-posts' => 'Update posts',
        'delete-posts' => 'Delete posts',
        'manage-users' => 'Manage users',
    ]);

    Passport::setDefaultScope([
        'read-posts',
    ]);
}

Protect Routes with Scopes

Route::middleware(['auth:api', 'scope:read-posts'])->group(function () {
    Route::get('/posts', [PostController::class, 'index']);
});

Route::middleware(['auth:api', 'scope:create-posts'])->group(function () {
    Route::post('/posts', [PostController::class, 'store']);
});

Route::middleware(['auth:api', 'scopes:update-posts,delete-posts'])->group(function () {
    Route::put('/posts/{post}', [PostController::class, 'update']);
    Route::delete('/posts/{post}', [PostController::class, 'destroy']);
});

Check Scopes in Controllers

public function store(Request $request)
{
    if (!$request->user()->tokenCan('create-posts')) {
        return response()->json([
            'error' => 'Insufficient permissions',
        ], 403);
    }

    // Create post logic
}

Authorization Code Grant

Authorization code grant is for third-party applications that need user authorization.

Create OAuth Client

php artisan passport:client

Select "Authorization Code Grant" and provide the redirect URI.

Authorization Request

Direct users to the authorization endpoint:

https://your-app.test/oauth/authorize?
  client_id=your-client-id&
  redirect_uri=https://client-app.test/callback&
  response_type=code&
  scope=read-posts create-posts&
  state=random-state-string

Exchange Authorization Code for Token

curl -X POST http://your-app.test/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "client_id": "your-client-id",
    "client_secret": "your-client-secret",
    "redirect_uri": "https://client-app.test/callback",
    "code": "authorization-code"
  }'

Protecting API Routes

Basic Middleware Protection

Route::middleware('auth:api')->group(function () {
    Route::apiResource('posts', PostController::class);
    Route::apiResource('comments', CommentController::class);
});

Multiple Guard Protection

Route::middleware(['auth:api', 'throttle:60,1'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

Custom Middleware for Token Validation

php artisan make:middleware ValidateApiToken
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;

class ValidateApiToken
{
    public function handle(Request $request, Closure $next): Response
    {
        if (!$request->user() || !$request->user()->token()) {
            return response()->json([
                'error' => 'Unauthenticated',
            ], 401);
        }

        // Check if token is expired
        if ($request->user()->token()->expires_at < now()) {
            return response()->json([
                'error' => 'Token expired',
            ], 401);
        }

        return $next($request);
    }
}

Security Best Practices

1. Use HTTPS in Production

Always use HTTPS for API endpoints:

if (app()->environment('production')) {
    URL::forceScheme('https');
}

2. Set Token Expiration

Configure appropriate token lifetimes:

Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::personalAccessTokensExpireIn(now()->addMonths(6));

3. Rate Limiting

Protect against brute force attacks:

Route::middleware(['auth:api', 'throttle:60,1'])->group(function () {
    // 60 requests per minute
});

4. Token Revocation

Implement logout to revoke tokens:

public function logout(Request $request)
{
    $request->user()->token()->revoke();
    
    // Revoke all tokens
    $request->user()->tokens()->delete();
    
    return response()->json(['message' => 'Logged out']);
}

5. CORS Configuration

Configure CORS in config/cors.php:

return [
    'paths' => ['api/*', 'oauth/*'],
    'allowed_methods' => ['*'],
    'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')],
    'allowed_headers' => ['*'],
    'supports_credentials' => true,
];

6. Encrypt Client Secrets

Store client secrets in environment variables:

PASSPORT_PASSWORD_CLIENT_ID=your-client-id
PASSPORT_PASSWORD_CLIENT_SECRET=your-client-secret

Testing API Authentication

Feature Test Example

namespace Tests\Feature;

use App\Models\User;
use Laravel\Passport\Passport;
use Tests\TestCase;

class ApiAuthenticationTest extends TestCase
{
    public function test_user_can_access_protected_route()
    {
        $user = User::factory()->create();
        
        Passport::actingAs($user, ['read-posts']);

        $response = $this->getJson('/api/posts');

        $response->assertStatus(200);
    }

    public function test_unauthenticated_user_cannot_access_protected_route()
    {
        $response = $this->getJson('/api/posts');

        $response->assertStatus(401);
    }

    public function test_user_without_scope_cannot_create_post()
    {
        $user = User::factory()->create();
        
        Passport::actingAs($user, ['read-posts']);

        $response = $this->postJson('/api/posts', [
            'title' => 'Test Post',
            'content' => 'Test content',
        ]);

        $response->assertStatus(403);
    }
}

Conclusion

Implementing OAuth2 authentication in Laravel 12 with Passport provides enterprise-grade API security with minimal configuration. From personal access tokens for mobile apps to authorization code grants for third-party integrations, Passport covers all OAuth2 use cases while maintaining Laravel's elegant syntax.

Key takeaways:

  • Install and configure Laravel Passport for OAuth2 authentication
  • Use personal access tokens for first-party applications
  • Implement password grant for trusted clients
  • Protect API routes with scopes for granular permissions
  • Follow security best practices for production deployments
  • Test authentication flows thoroughly

Need help building secure APIs for your Laravel application? NeedLaravelSite specializes in developing scalable, secure Laravel APIs with proper authentication. Contact us for expert Laravel development services.


Related Resources:


Article Tags

Laravel Passport Laravel OAuth2 Laravel API authentication Laravel 12 API security OAuth2 implementation Laravel token authentication API security Laravel Laravel personal access tokens Laravel authorization code Laravel API scopes REST API authentication Laravel Bearer tokens

About the Author

Muhammad Waqas

Muhammad Waqas

CEO at CentoSquare

Founder & CEO at CentoSquare | Creator of NeedLaravelSite | Helping Businesses Grow with Cutting-Edge Web, Mobile & Marketing Solutions | Building Innovative Products