Laravel Passport实现SPA客户端支持客户端凭证与个人访问令牌
Hey there! Let's break down how to implement your exact requirements with Laravel Passport and your SPA. You've got two distinct API access scenarios, so we'll leverage two specific Passport OAuth2 grant types to cover both cleanly:
First, let's map your needs to the right Passport patterns:
- Endpoints open only to valid clients (no user login required): Use the Client Credentials Grant (this authenticates the SPA itself, not a user)
- Endpoints requiring user login: Use the Authorization Code Grant with PKCE (the secure standard for SPAs, since we can't safely store a client secret in browser-based apps)
Step 1: Base Passport Setup
First, get Passport up and running in your Laravel app:
- Install Passport and run migrations:
composer require laravel/passport php artisan migrate php artisan passport:install
- Add the
HasApiTokenstrait to yourUsermodel to enable token-based authentication for users:
// app/Models/User.php use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; }
- Register Passport routes in your
AuthServiceProvider:
// app/Providers/AuthServiceProvider.php use Laravel\Passport\Passport; public function boot() { $this->registerPolicies(); Passport::routes(); // Optional: Set token expiration times (adjust as needed) Passport::tokensExpireIn(now()->addDays(15)); Passport::refreshTokensExpireIn(now()->addDays(30)); }
- Update your
config/auth.phpto use Passport for the API guard:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
Step 2: Configure Client-Only API Endpoints
These endpoints don't need a logged-in user—they just need to verify that the request is coming from your trusted SPA.
- Create a dedicated client for this grant type:
php artisan passport:client --client
Save the generated client_id and client_secret in your SPA's environment variables (e.g., .env for Vue/React/Vite apps) — never hardcode these values!
- Define protected routes with the
clientmiddleware:
This middleware validates the client's access token without checking for a user:
// routes/api.php Route::middleware('client')->group(function () { // Add your client-only endpoints here Route::get('/app/config', 'Api\AppConfigController@index'); Route::post('/contact/submit', 'Api\ContactController@store'); });
- Fetch a client token in your SPA:
Send a POST request to Laravel's OAuth token endpoint to get a valid client token, then store it (e.g., inlocalStorage):
// Example in a Vue/React component async function getClientToken() { const response = await fetch('/oauth/token', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ grant_type: 'client_credentials', client_id: import.meta.env.VITE_CLIENT_ID, client_secret: import.meta.env.VITE_CLIENT_SECRET, scope: '', // Leave empty if no scopes are needed }), }); const data = await response.json(); localStorage.setItem('client_token', data.access_token); } // Use the token for client-only API calls async function fetchAppConfig() { const token = localStorage.getItem('client_token'); const response = await fetch('/api/app/config', { headers: { 'Authorization': `Bearer ${token}`, }, }); return response.json(); }
Step 3: Configure User-Authenticated API Endpoints
These endpoints require a logged-in user, so we'll use the PKCE-enhanced Authorization Code Grant (the safest option for SPAs).
- Create an SPA-specific client (no secret required):
php artisan passport:client --spa
Save the generated client_id in your SPA's environment variables.
- Define user-protected routes with the
auth:apimiddleware:
This middleware validates that the request includes a valid user access token:
// routes/api.php Route::middleware('auth:api')->group(function () { // Add your user-specific endpoints here Route::get('/user/profile', 'Api\UserProfileController@show'); Route::put('/user/profile', 'Api\UserProfileController@update'); Route::get('/user/orders', 'Api\UserOrderController@index'); });
- Implement user login & token handling in your SPA:
Use Laravel's official@laravel/passportnpm package to simplify the OAuth flow:
npm install @laravel/passport
Then set up the auth provider and handle the login/callback flow:
// Example in a Vue/React app import { createAuthProvider } from '@laravel/passport'; const authProvider = createAuthProvider({ clientId: import.meta.env.VITE_SPA_CLIENT_ID, authorizeEndpoint: '/oauth/authorize', tokenEndpoint: '/oauth/token', redirectUri: `${window.location.origin}/callback`, // Your SPA's callback route scopes: [], // Add scopes if you need granular permissions }); // Trigger the login flow (redirects to Laravel's login page) async function login() { await authProvider.login(); } // Handle the OAuth callback to get the user token async function handleCallback() { const { token } = await authProvider.handleCallback(); localStorage.setItem('user_token', token.access_token); // Redirect back to your app's homepage window.location.href = '/'; } // Use the user token for authenticated API calls async function fetchUserProfile() { const token = localStorage.getItem('user_token'); const response = await fetch('/api/user/profile', { headers: { 'Authorization': `Bearer ${token}`, }, }); return response.json(); }
- Update CORS settings:
Make sure yourconfig/cors.phpallows your SPA's domain to access the OAuth and API routes:
'paths' => ['api/*', 'oauth/*', 'login', 'logout'], 'allowed_origins' => [env('SPA_URL', 'http://localhost:3000')], 'allowed_methods' => ['*'], 'allowed_headers' => ['*'], 'supports_credentials' => true,
Bonus Tips
- Token Refresh: Both client and user tokens can be refreshed using the
refresh_tokenreturned in the token response—this avoids forcing users to log in repeatedly. - Scopes: For granular access control, define scopes in Passport and enforce them on routes with the
scopemiddleware (e.g.,Route::middleware(['auth:api', 'scope:manage-orders'])). - Security: For the client credentials grant, keep your
client_secretsafe—use environment variables and avoid committing them to version control.
内容的提问来源于stack exchange,提问作者jmardz

