Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

Laravel 13 REST API Authentication with Passport: Step-by-Step Tutorial

๐Ÿ› ๏ธ Laravel Tutorials5 min read

Laravel 13 REST API Authentication with Passport: Step-by-Step Tutorial

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 10, 2026
Laravel 13 REST API Authentication with Passport: Step-by-Step Tutorial

To add REST API authentication with Laravel Passport in Laravel 13, run php artisan install:api --passport, implement OAuthenticatable on the User model, set the api guard driver to passport, and protect routes with auth:api. Users then receive an access token on login and send it as a Bearer header.

This guide targets Laravel 13 (PHP 8.3 to 8.5) and the setup steps documented for Laravel Passport on the 13.x docs. The code was written for this article and not run against a live project, so test it in a fresh app first.

When should you choose Passport instead of Sanctum?

Passport is a full OAuth2 server. Pick it when third-party applications must request access on behalf of users, or when you need OAuth2 grants and scopes. For a first-party mobile app or single-page app that only needs simple tokens, Sanctum is lighter. This article uses personal access tokens, the simplest Passport flow for a login endpoint.

Prerequisites

  • PHP 8.3+, Composer and a Laravel 13 project with a configured database.
  • The default users table migrated.
  • An API client such as Postman, Insomnia or curl.

Step 1: How do you install Passport?

Run the install command. It publishes the API routes file, installs Passport, runs its migrations and generates the encryption keys.

Code
php artisan install:api --passport

If prompted, accept running the migrations. Afterwards, create a personal access client once:

Code
php artisan passport:client --personal

Step 2: How do you update the User model?

Add the HasApiTokens trait and implement the OAuthenticatable contract.

JavaScript
namespace App\Models;

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

class User extends Authenticatable implements OAuthenticatable
{
    use HasApiTokens, Notifiable;

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

    protected function casts(): array
    {
        return ['password' => 'hashed'];
    }
}

Step 3: How do you configure the API guard?

Open config/auth.php and add an api guard that uses the passport driver.

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

Step 4: How do you build register and login endpoints?

Create the controller with php artisan make:controller Api/AuthController.

JavaScript
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthController extends Controller
{
    public function register(Request $request): JsonResponse
    {
        $data = $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'unique:users'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);

        $user = User::create($data);

        return response()->json([
            'user' => $user,
            'token' => $user->createToken('api')->accessToken,
        ], 201);
    }

    public function login(Request $request): JsonResponse
    {
        $credentials = $request->validate([
            'email' => ['required', 'email'],
            'password' => ['required'],
        ]);

        if (! Auth::attempt($credentials)) {
            return response()->json(['message' => 'Invalid credentials.'], 401);
        }

        $user = $request->user();

        return response()->json([
            'user' => $user,
            'token' => $user->createToken('api')->accessToken,
        ]);
    }

    public function logout(Request $request): JsonResponse
    {
        $request->user()->token()->revoke();

        return response()->json(['message' => 'Logged out.']);
    }
}

The model's hashed cast hashes the password on create, so the controller never calls Hash::make(). Auth::attempt() here uses the default web guard to check the credentials; the token itself is created by Passport.

Step 5: How do you protect the routes?

In routes/api.php add the public and protected routes. Protected ones use the auth:api middleware.

JavaScript
use App\Http\Controllers\Api\AuthController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

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

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

How do you call the API?

Send JSON and ask for JSON responses. Replace the token with the value returned by login.

Shell
curl -X POST http://localhost:8000/api/login \
  -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"secret-pass"}'

curl http://localhost:8000/api/user \
  -H "Accept: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN"

How do you limit what a token can do?

Pass scopes to createToken(), for example $user->createToken('api', ['orders:read']). Declare allowed scopes with Passport::tokensCan([...]) in a service provider and check them with Passport's scope middleware on routes that need them. See the Passport documentation for the exact middleware aliases in your version.

Common mistakes

  • Skipping the personal client. createToken() fails until passport:client --personal has been run.
  • Wrong guard. Routes protected with auth instead of auth:api look for a session, so API calls redirect or return 401.
  • Missing Accept header. Without Accept: application/json, authentication failures may redirect to a login page instead of returning JSON 401.
  • Different keys per server. Behind a load balancer, every server must share the same Passport keys, or tokens issued by one are rejected by another.
  • Committing keys. The generated key files are secrets; keep them out of version control.

How do you verify the setup?

  1. Register a user and confirm you get a 201 response with a token.
  2. Call GET /api/user with the token and confirm you get the user JSON.
  3. Call it without the header and confirm a 401.
  4. Call POST /api/logout, then reuse the old token and confirm a 401.

FAQ

Is Passport or Sanctum better for a mobile app?

Sanctum is usually simpler for your own first-party app. Choose Passport when you need a full OAuth2 server.

How do I revoke a token?

Call $request->user()->token()->revoke() for the current token.

Why do I get "Unauthenticated" with a valid token?

Check that the route uses auth:api, the guard driver is passport, and the header is exactly Authorization: Bearer TOKEN.

References

Article Index & Topic Tags
LaravelAuthenticationApiPassport

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.