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.
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:
- OAuth2: An industry-standard protocol for authorization
- Grant Types: Different ways clients can obtain access tokens
- Access Tokens: Temporary credentials used to access protected resources
- Refresh Tokens: Long-lived tokens that can obtain new access tokens
- Scopes: Permissions that define what a token can access
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:
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
- Token Security: Always use HTTPS for API requests to protect tokens in transit
- Token Storage: Store tokens securely on the client (e.g., HttpOnly cookies for web apps)
- Scope Granularity: Design scopes with "principle of least privilege" in mind
- Reasonable Lifetimes: Set appropriate expiration times for different token types
- Error Handling: Return meaningful error messages for authentication failures
- Rate Limiting: Combine with Laravel's rate limiting to prevent brute force attacks
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:
- Secure your API with token-based authentication
- Support multiple authentication flows for different client types
- Add fine-grained access control with scopes
- Give users control over their tokens and sessions
- Easily test authenticated API requests
By implementing Passport, you can create secure, robust APIs that safely expose your application's functionality to authenticated clients.