laravel-passport
Auto-invoked skill
Implement full OAuth2 server with all grant types
Trigger Keywords
This skill automatically activates when Claude detects these keywords:
passport
oauth2 server
authorization code
client credentials
access token
refresh token
oauth provider
Overview
The laravel-passport skill provides expertise for building a complete OAuth2 authorization server. It covers all OAuth2 grant types, token scopes, client management, and third-party API access.
What This Skill Provides
- OAuth2 Grant Types - Authorization Code, Client Credentials, PKCE
- Token Management - Access tokens, refresh tokens, revocation
- Client Management - Create and manage OAuth clients
- Token Scopes - Fine-grained permission control
- Personal Access Tokens - User-generated API tokens
- Third-Party Access - Allow external apps to access your API
When to Use
Use Passport when:
- Building an OAuth2 authorization server
- Third-party applications need to access your API
- Need all OAuth2 grant types
- Require refresh tokens with rotation
Use Sanctum instead when:
- Building first-party SPAs or mobile apps
- Simple token authentication is sufficient
- Don't need OAuth2 complexity
Quick Start
composer require laravel/passport
php artisan migrate
php artisan passport:install
User Model Setup
<?php
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
}
Configure Auth Guard
<?php
// config/auth.php
return [
'guards' => [
'api' => [
'driver' => 'passport', // Changed from 'token'
'provider' => 'users',
],
],
];
Authorization Code Grant
Most secure option for third-party web applications.
Create Client
php artisan passport:client
Authorization Request
GET /oauth/authorize?client_id=1
&redirect_uri=https://client-app.com/callback
&response_type=code
&scope=read-posts write-posts
&state=random_state_string
Exchange Code for Token
POST /oauth/token
Content-Type: application/json
{
"grant_type": "authorization_code",
"client_id": "1",
"client_secret": "abc123...",
"redirect_uri": "https://client-app.com/callback",
"code": "def456..."
}
Client Credentials Grant
For machine-to-machine communication without user context.
# Create machine client
php artisan passport:client --client
POST /oauth/token
{
"grant_type": "client_credentials",
"client_id": "2",
"client_secret": "xyz789...",
"scope": "read-data"
}
// Protect routes with client middleware
Route::middleware(['client'])->group(function () {
Route::get('/api/stats', [StatsController::class, 'index']);
});
Authorization Code with PKCE
Recommended for SPAs and mobile apps (no client secret needed).
// Generate code_verifier and code_challenge
const codeVerifier = generateRandomString(128);
const codeChallenge = await sha256(codeVerifier);
// Authorization request with PKCE
GET /oauth/authorize?client_id=5
&redirect_uri=https://app.com/callback
&response_type=code
&scope=read-posts
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
POST /oauth/token
{
"grant_type": "authorization_code",
"client_id": "5",
"redirect_uri": "https://app.com/callback",
"code": "def456...",
"code_verifier": "original_verifier"
}
Token Scopes
<?php
// app/Providers/AuthServiceProvider.php
use Laravel\Passport\Passport;
public function boot(): void
{
Passport::tokensCan([
'read-posts' => 'Read posts',
'write-posts' => 'Create and edit posts',
'delete-posts' => 'Delete posts',
'admin' => 'Full administrative access',
]);
Passport::setDefaultScope(['read-posts']);
}
Check Scopes in Controllers
<?php
public function store(Request $request)
{
if (! $request->user()->tokenCan('write-posts')) {
return response()->json(['error' => 'Insufficient permissions'], 403);
}
return Post::create($request->validated());
}
Scope Middleware
// Require any of these scopes
Route::middleware(['auth:api', 'scopes:write-posts,admin'])->group(function () {
Route::post('/posts', [PostController::class, 'store']);
});
// Require all scopes
Route::middleware(['auth:api', 'scope:write-posts,admin'])->group(function () {
Route::delete('/posts/{post}', [PostController::class, 'destroy']);
});
Personal Access Tokens
<?php
// Create token via code
$token = $user->createToken(
'My API Token',
['read-posts', 'write-posts']
);
return response()->json([
'token' => $token->accessToken,
]);
Token Lifetimes
<?php
// app/Providers/AuthServiceProvider.php
public function boot(): void
{
// Access tokens expire in 15 days
Passport::tokensExpireIn(now()->addDays(15));
// Refresh tokens expire in 30 days
Passport::refreshTokensExpireIn(now()->addDays(30));
// Personal access tokens expire in 6 months
Passport::personalAccessTokensExpireIn(now()->addMonths(6));
// Prune revoked tokens
Passport::pruneRevokedTokens();
}
Revoking Tokens
// Revoke current access token
$request->user()->token()->revoke();
// Revoke all user tokens
$user->tokens->each->revoke();
// Revoke specific token
$token = $user->tokens()->find($tokenId);
$token->revoke();
Refresh Tokens
async function refreshAccessToken(refreshToken) {
const response = await fetch('/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: clientId,
client_secret: clientSecret,
}),
});
const data = await response.json();
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
};
}
Testing OAuth Flows
<?php
use Laravel\Passport\Passport;
it('allows access with correct scope', function () {
$user = User::factory()->create();
Passport::actingAs($user, ['write-posts']);
$response = $this->postJson('/api/posts', [
'title' => 'Test Post',
'body' => 'Content',
]);
$response->assertCreated();
});
it('denies access without required scope', function () {
$user = User::factory()->create();
Passport::actingAs($user, ['read-posts']);
$response = $this->postJson('/api/posts', [
'title' => 'Test Post',
]);
$response->assertForbidden();
});
Common Pitfalls
- Not running passport:install - Must run after migration to generate keys
- Wrong auth guard - Must use
'driver' => 'passport'not'token' - Missing HasApiTokens trait - Use
Laravel\Passport\HasApiTokens - Exposing client secrets - Never expose in frontend, use PKCE instead
- No token lifetimes - Always configure expiration in production
- Password grant for third-party - Only for first-party apps (deprecated)
- Not validating redirect URIs - Whitelist exact URIs to prevent token theft
- Forgetting to revoke on security events - Revoke on password change
- Not using HTTPS in production - OAuth2 requires HTTPS
- Not pruning revoked tokens - Schedule
passport:purgedaily
Best Practices
- Use Authorization Code + PKCE for SPAs and mobile apps
- Use Client Credentials for machine-to-machine
- Never use Password Grant for third-party apps
- Always use HTTPS in production
- Set reasonable token lifetimes
- Implement token refresh logic
- Revoke tokens on security events (password change)
- Whitelist exact redirect URIs per client
- Use scopes to limit token permissions
- Prune revoked tokens regularly
- Rate limit token endpoints
- Store refresh tokens securely
- Never expose client secrets in frontend
Related Commands
# Install Passport
php artisan passport:install
# Create OAuth client
php artisan passport:client
# Create password grant client
php artisan passport:client --password
# Create client credentials client
php artisan passport:client --client
# Purge revoked/expired tokens
php artisan passport:purge
# Generate encryption keys
php artisan passport:keys
Related Skills
- laravel-sanctum - Lightweight API authentication
- laravel-api - Building REST APIs
- laravel-auth - Authentication and authorization
- laravel-security - Security best practices