Key Takeaways
Picture this. You're six months into building your Laravel mobile backend. Everything was smooth until you needed third-party apps to access your API. Now you're drowning in OAuth2 RFC documents, wondering if there's a simpler way. Spoiler: there is.
Laravel Passport turns the notoriously complex OAuth2 implementation into a few Artisan commands. But the documentation assumes you already know what you're doing — it skips over the setup headaches, the grant type confusion, and the production gotchas that bite you at 2 AM. This guide walks you through Laravel Passport setup the way you actually need it: practical, production-ready, and explained without assuming you're an OAuth2 expert.
The OAuth2 Problem Nobody Talks About
Before we dive into Laravel Passport, let's address the elephant in the room: OAuth2 is complicated. The RFCs are dense, the terminology is abstract (grant types? authorization codes? scopes?), and the difference between what sounds like the same thing can mean hours of debugging.
The traditional way to implement OAuth2 in Laravel meant understanding the entire spec, choosing your grant types, managing token storage, implementing refresh flows, and building authorization pages from scratch. For most projects, this was overkill.
Laravel Passport changes this. It wraps the League OAuth2 server — the industry-standard implementation — in a Laravel package that handles the complexity. You get a complete OAuth2 server with a few Artisan commands.
But here's what the documentation glosses over: Passport doesn't decide which grant type you need. It doesn't set your token lifetimes for you. And it definitely doesn't tell you what scopes to use. Those decisions are still yours, and getting them wrong means insecure APIs or frustrated users.
At Boundev, we place senior Laravel developers who understand OAuth2 from the ground up — engineers who can architect your authentication strategy, not just copy-paste tutorials. This guide covers everything you need to know to do it right.
Understanding OAuth2 Grant Types
The most important decision you'll make with Laravel Passport is choosing the right grant type. Each grant type serves a different use case, and mixing them up creates security vulnerabilities or usability nightmares.
Password Grant: First-Party Applications
Use the password grant when you're building your own mobile app or SPA that talks to your own API. Your users already trust your application, so you can collect their credentials directly and exchange them for an access token.
This is the simplest flow for first-party applications. The user enters their username and password, your app sends them to the token endpoint, and Passport returns an access token.
// routes/api.php
Route::post('/login', function (Request $request) {
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required',
]);
if (!Auth::attempt($credentials)) {
return response()->json(['error' => 'Invalid credentials'], 401);
}
$token = Auth::user()->createToken($request->device_name)->accessToken;
return response()->json([
'access_token' => $token,
'token_type' => 'Bearer',
]);
});
For this to work, you need to enable the password grant in your AppServiceProvider:
// app/Providers/AppServiceProvider.php
use LaravelPassportPassport;
public function boot(): void
{
Passport::enablePasswordGrant();
}
Authorization Code Grant: Third-Party Applications
Use the authorization code grant when you want third-party developers to build apps that integrate with your API. This is the flow you've seen when logging into a site with Google or GitHub.
The user is redirected to your authorization page, approves access, and gets redirected back with an authorization code. The third-party app exchanges this code for an access token. The key benefit: the third-party app never sees the user's password.
This is more complex to implement but essential for public APIs where you want to control which applications can access what data.
Client Credentials Grant: Machine-to-Machine Communication
Use the client credentials grant for server-to-server communication where no user is involved. A background job needs to access your API? A microservice needs to talk to another? This grant type is for you.
php artisan passport:client --client
You'll get a client ID and secret. Use these to request tokens directly:
curl -X POST https://yourapi.com/oauth/token \
-d "grant_type=client_credentials" \
-d "client_id=your-client-id" \
-d "client_secret=your-client-secret"
Personal Access Tokens: Quick API Keys
For use cases that don't fit the standard flows — developer API keys, CLI tool authentication — Passport's personal access tokens work like API keys that users can generate from their account settings.
$token = $user->createToken('My API Key')->accessToken;
This doesn't require client credentials. It's perfect for internal services or developer integrations where you want something simpler than OAuth2 flows.
Building an API that needs OAuth2 authentication?
Boundev's Laravel teams architect authentication strategies that match your use case — from simple token-based auth to full OAuth2 flows with scopes, refresh tokens, and third-party integrations.
See How We Do ItInstalling and Configuring Laravel Passport
Now that you understand which grant type you need, let's get Passport installed. Laravel 11+ makes this straightforward with the install:api command:
composer require laravel/passport
php artisan install:api --passport
This single command handles everything: publishing migrations, generating encryption keys, and creating the required database tables for tokens and clients.
For Laravel 10 and earlier:
composer require laravel/passport
php artisan passport:install
php artisan migrate
Protecting Your API Routes
The auth:api middleware is your key to protecting routes. Add it to any route that needs authentication:
// routes/api.php
Route::middleware('auth:api')->group(function () {
Route::get('/user', function (Request $request) {
return $request->user();
});
Route::get('/profile', [ProfileController::class, 'show']);
Route::put('/profile', [ProfileController::class, 'update']);
});
Now these routes require a valid Bearer token. Requests without tokens get a 401 response automatically.
Configuring Token Lifetimes
Passport defaults to sensible token lifetimes, but you should customize them for your security requirements. Configure this in your AppServiceProvider:
use LaravelPassportPassport;
use CarbonCarbonInterval;
public function boot(): void
{
// Access tokens expire in 15 days
Passport::tokensExpireIn(CarbonInterval::days(15));
// Refresh tokens (for re-authenticating without credentials) expire in 30 days
Passport::refreshTokensExpireIn(CarbonInterval::days(30));
// Personal access tokens expire in 6 months
Passport::personalAccessTokensExpireIn(CarbonInterval::months(6));
}
Longer token lifetimes mean fewer login prompts for users but longer windows of exposure if a token is compromised. Balance usability against security based on your application's risk profile.
Implementing Scopes: The Principle of Least Privilege
Scopes are permissions attached to access tokens. They let you define exactly what each token can do — and nothing more.
Imagine your API has user data, payment data, and admin functions. You don't want a mobile app that only reads user profiles to have access to admin functions. Scopes solve this.
Defining Scopes
Register your scopes in AuthServiceProvider:
// app/Providers/AuthServiceProvider.php
use LaravelPassportPassport;
protected $policies = [
'App\Models\Order' => 'App\Policies\OrderPolicy',
];
public function boot(): void
{
$this->registerPolicies();
Passport::tokensCan([
'profile' => 'Read user profile',
'orders' => 'Read order history',
'payments' => 'Access payment information',
'admin' => 'Administrative functions',
]);
Passport::setDefaultScope([
'profile',
]);
}
Checking Scopes in Routes
Protect routes by requiring specific scopes:
Route::middleware(['auth:api', 'scope:admin'])->group(function () {
Route::get('/admin/users', [AdminController::class, 'users']);
Route::delete('/admin/users/{id}', [AdminController::class, 'destroy']);
});
Route::middleware(['auth:api', 'scope:orders'])->group(function () {
Route::get('/orders', [OrderController::class, 'index']);
Route::get('/orders/{id}', [OrderController::class, 'show']);
});
A token with the orders scope can access order endpoints but gets a 403 Forbidden on admin endpoints. This is the principle of least privilege in action — grant only the permissions needed for each use case.
Building Your Authentication API
Let's put this together into a complete authentication API with registration, login, and token management.
User Registration
public function register(Request $request)
{
$validated = $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' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken('auth_token')->accessToken;
return response()->json([
'user' => $user,
'access_token' => $token,
'token_type' => 'Bearer',
], 201);
}
User Login
public function login(Request $request)
{
$credentials = $request->validate([
'email' => 'required|email',
'password' => 'required',
'device_name' => 'required',
]);
if (!Auth::attempt($credentials)) {
return response()->json([
'message' => 'Invalid credentials'
], 401);
}
$user = Auth::user();
// Revoke previous tokens (optional — prevents token accumulation)
$user->tokens()->delete();
$token = $user->createToken($request->device_name)->accessToken;
return response()->json([
'user' => $user,
'access_token' => $token,
'token_type' => 'Bearer',
]);
}
Logging Out
Revoke the current token when the user logs out:
public function logout(Request $request)
{
$request->user()->token()->revoke();
return response()->json([
'message' => 'Successfully logged out'
]);
}
Listing Active Tokens
Give users visibility into where they're logged in:
public function tokens(Request $request)
{
return response()->json([
'tokens' => $request->user()->tokens->map(function ($token) {
return [
'id' => $token->id,
'name' => $token->name,
'created_at' => $token->created_at,
'expires_at' => $token->expires_at,
];
})
]);
}
This is a critical security feature. Users should be able to see all active sessions and revoke ones they don't recognize.
Need Laravel Developers Who Know OAuth2 Inside Out?
Boundev places senior Laravel engineers with proven OAuth2 implementation experience — from simple token auth to complex third-party integrations with scopes and refresh flows.
Talk to Our TeamTesting Your Authentication
Passport provides testing helpers that make it easy to write authentication tests:
use LaravelPassportPassport;
public function test_user_can_get_profile()
{
Passport::actingAs(
User::factory()->create(),
['profile']
);
$response = $this->getJson('/api/profile');
$response->assertOk()
->assertJsonStructure([
'id',
'name',
'email',
]);
}
public function test_unauthenticated_user_cannot_access_protected_route()
{
$response = $this->getJson('/api/profile');
$response->assertStatus(401);
}
public function test_token_without_scope_cannot_access_admin()
{
Passport::actingAs(
User::factory()->create(),
['profile'] // Note: no 'admin' scope
);
$response = $this->getJson('/api/admin/users');
$response->assertStatus(403);
}
The Passport::actingAs() method creates an authenticated user with specific scopes, making it straightforward to test different permission levels.
Production Security Checklist
Before you go live with Laravel Passport, run through this checklist:
Always use HTTPS. OAuth2 tokens are bearer tokens — anyone with the token can use it. HTTPS encrypts the transmission and prevents token interception.
Set appropriate token lifetimes. Shorter is more secure but creates more friction. Balance based on your use case.
Implement token revocation. Users need the ability to invalidate compromised tokens. Passport handles this automatically for the current token, but consider building UI for managing all active tokens.
Clean up expired tokens. Schedule the Passport purge command to remove old tokens:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
$schedule->command('passport:purge')->daily();
}
Validate scopes at every endpoint. Don't assume a token with access to /api/ can access everything. Define specific scopes for each sensitive operation.
How Boundev Solves This for You
Everything we've covered — grant type selection, scope architecture, token management, production hardening — is exactly what our Laravel teams implement from day one. Here's how we approach OAuth2 authentication for our clients.
We build you a full remote Laravel team with OAuth2 specialists — architects who design your authentication strategy from scratch.
Add Laravel developers with OAuth2 implementation experience to your team — engineers who architect scopes and implement token management correctly.
Hand us a Laravel application with authentication problems. We audit, architect, and implement OAuth2 correctly — from grant types to scope hierarchies.
The Bottom Line
OAuth2 doesn't have to be overwhelming. Laravel Passport abstracts the complexity of the League OAuth2 server into a Laravel-native package that handles tokens, scopes, and authorization flows with Artisan commands.
The key decisions that matter: choose the right grant type for your use case, define scopes following least privilege, give users visibility into their active tokens, and always use HTTPS in production.
The Bottom Line
Password grant for your own apps. Authorization code for third parties. Client credentials for services. Personal access tokens for API keys. Laravel Passport handles the rest.
Need help implementing Laravel Passport correctly?
Boundev's Laravel teams architect OAuth2 authentication that matches your use case — from simple token auth to complex scope hierarchies and third-party integrations.
See How We Do ItFrequently Asked Questions
What's the difference between Laravel Passport and Laravel Sanctum?
Passport is a full OAuth2 server implementation — it supports multiple grant types (password, authorization code, client credentials), scopes, and third-party authentication. Sanctum is simpler — it uses API tokens or SPA session cookies and doesn't support OAuth2 flows. Use Passport when you need OAuth2 features or third-party app integration. Use Sanctum for simpler cases like a SPA authenticating with your own API.
How do I choose the right grant type for my application?
Use password grant for your own mobile apps and SPAs that talk to your own API — users enter credentials directly and get tokens. Use authorization code grant for third-party apps that need access to your API — they redirect users to your authorization page. Use client credentials for machine-to-machine communication with no user involved. Use personal access tokens for developer API keys or situations where you want something simpler than OAuth2 flows.
What are OAuth2 scopes and why do they matter?
Scopes are permissions attached to access tokens that limit what each token can do. For example, a token with the "read-only" scope can GET data but not POST, PUT, or DELETE. Scopes follow the principle of least privilege — grant tokens only the permissions they need. This prevents compromised tokens from having full access to your API.
How do I implement token revocation for users?
Passport stores tokens in the database and provides methods to revoke them. Revoke the current token with $user->token()->revoke(). Revoke all tokens with $user->tokens()->delete(). Passport also provides the /oauth/token/revoke endpoint for clients to revoke tokens they no longer need. Schedule php artisan passport:purge daily to clean up expired and revoked tokens.
What are appropriate token lifetimes for production?
It depends on your security requirements and user experience tradeoffs. Access tokens typically expire in 1 hour to 2 weeks. Refresh tokens (used to get new access tokens without re-authenticating) typically expire in 7 days to 30 days. Shorter lifetimes are more secure but require more frequent re-authentication. For high-security applications, use shorter lifetimes. For consumer apps prioritizing convenience, longer lifetimes with refresh token rotation work well.
Explore Boundev's Services
Ready to implement Laravel Passport the right way? Here's how we can help.
Build a full remote Laravel team with OAuth2 specialists who architect authentication from scratch.
Learn more →
Add senior Laravel engineers with OAuth2 implementation experience to your team.
Learn more →
Outsource your Laravel authentication and get OAuth2 implemented correctly from the start.
Learn more →
Let's Build Laravel Authentication That Scales
You now know exactly what Laravel Passport can do. The next step is having the team that implements it correctly.
200+ companies have trusted us to build their engineering teams. Tell us what you need — we'll respond within 24 hours.
