Laravel Passport
- Introduction
- Installation
- Configuration
- Issuing Access Tokens
- Authorization Code Grant With PKCE
- Password Grant Tokens
- Implicit Grant Tokens
- Client Credentials Grant Tokens
- Personal Access Tokens
- Protecting Routes
- Token Scopes
- Consuming Your API With JavaScript
- Events
- Testing
Introduction
Laravel Passport 讓您的 Laravel 應用程式在幾分鐘內提供完整的 OAuth2 伺服器實作。Passport 建構於由 Andy Millington 和 Simon Hamp 維護的 League OAuth2 server 之上。
本文件假設您已熟悉 OAuth2。如果您對 OAuth2 一無所知,請考慮先熟悉 OAuth2 的一般術語和功能,再繼續。
Passport or Sanctum?
在開始之前,您可能希望確定您的應用程式更適合使用 Laravel Passport 還是 Laravel Sanctum。如果您的應用程式絕對需要支援 OAuth2,那麼您應該使用 Laravel Passport。
但是,如果您嘗試驗證單頁應用程式、行動應用程式或發行 API 令牌,則應使用 Laravel Sanctum。Laravel Sanctum 不支援 OAuth2;但是,它提供了更簡單的 API 身份驗證開發體驗。
Installation
您可以透過 install:api
Artisan 命令安裝 Laravel Passport
1php artisan install:api --passport
此命令將發布並執行資料庫遷移,這是建立應用程式儲存 OAuth2 用戶端和存取令牌所需的表格所必需的。該命令還將建立產生安全存取令牌所需的加密金鑰。
此外,此命令會詢問您是否希望使用 UUID 作為 Passport Client
模型的主鍵值,而不是自動遞增整數。
執行 install:api
命令後,將 Laravel\Passport\HasApiTokens
trait 新增至您的 App\Models\User
模型。此 trait 將為您的模型提供一些輔助方法,讓您可以檢查已驗證使用者的令牌和範圍
1<?php 2 3namespace App\Models; 4 5use Illuminate\Database\Eloquent\Factories\HasFactory; 6use Illuminate\Foundation\Auth\User as Authenticatable; 7use Illuminate\Notifications\Notifiable; 8use Laravel\Passport\HasApiTokens; 9 10class User extends Authenticatable11{12 use HasApiTokens, HasFactory, Notifiable;13}
最後,在您應用程式的 config/auth.php
設定檔中,您應該定義一個 api
身份驗證守衛,並將 driver
選項設定為 passport
。這將指示您的應用程式在驗證傳入的 API 請求時使用 Passport 的 TokenGuard
1'guards' => [ 2 'web' => [ 3 'driver' => 'session', 4 'provider' => 'users', 5 ], 6 7 'api' => [ 8 'driver' => 'passport', 9 'provider' => 'users',10 ],11],
Deploying Passport
首次將 Passport 部署到應用程式伺服器時,您可能需要執行 passport:keys
命令。此命令會產生 Passport 產生存取令牌所需的加密金鑰。產生的金鑰通常不會保留在原始碼控制中
1php artisan passport:keys
如有必要,您可以定義應從何處載入 Passport 金鑰的路徑。您可以使用 Passport::loadKeysFrom
方法來完成此操作。通常,此方法應從應用程式 App\Providers\AppServiceProvider
類別的 boot
方法中呼叫
1/**2 * Bootstrap any application services.3 */4public function boot(): void5{6 Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');7}
從環境載入金鑰
或者,您可以使用 vendor:publish
Artisan 命令發布 Passport 的設定檔
1php artisan vendor:publish --tag=passport-config
發布設定檔後,您可以透過將加密金鑰定義為環境變數來載入應用程式的加密金鑰
1PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----2<private key here>3-----END RSA PRIVATE KEY-----"4 5PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----6<public key here>7-----END PUBLIC KEY-----"
Upgrading Passport
升級到新主要版本的 Passport 時,請務必仔細查看升級指南。
Configuration
Client Secret Hashing
如果您希望在用戶端的密鑰儲存在資料庫中時進行雜湊處理,您應該在 App\Providers\AppServiceProvider
類別的 boot
方法中呼叫 Passport::hashClientSecrets
方法
1use Laravel\Passport\Passport;2 3Passport::hashClientSecrets();
啟用後,您的所有用戶端密鑰只會在建立後立即向使用者顯示。由於純文字用戶端密鑰值永遠不會儲存在資料庫中,因此如果密鑰遺失,則無法還原密鑰的值。
Token Lifetimes
預設情況下,Passport 發行長期存取令牌,這些令牌在一年後過期。如果您想要設定更長/更短的令牌生命週期,您可以使用 tokensExpireIn
、refreshTokensExpireIn
和 personalAccessTokensExpireIn
方法。這些方法應從應用程式 App\Providers\AppServiceProvider
類別的 boot
方法中呼叫
1/**2 * Bootstrap any application services.3 */4public function boot(): void5{6 Passport::tokensExpireIn(now()->addDays(15));7 Passport::refreshTokensExpireIn(now()->addDays(30));8 Passport::personalAccessTokensExpireIn(now()->addMonths(6));9}
Passport 資料庫表格上的 expires_at
資料行是唯讀的,僅供顯示之用。發行令牌時,Passport 會將到期資訊儲存在已簽署和加密的令牌中。如果您需要使令牌失效,您應該撤銷它。
Overriding Default Models
您可以自由擴充 Passport 內部使用的模型,方法是定義您自己的模型並擴充對應的 Passport 模型
1use Laravel\Passport\Client as PassportClient;2 3class Client extends PassportClient4{5 // ...6}
定義模型後,您可以指示 Passport 透過 Laravel\Passport\Passport
類別使用您的自訂模型。通常,您應該在應用程式 App\Providers\AppServiceProvider
類別的 boot
方法中告知 Passport 您的自訂模型
1use App\Models\Passport\AuthCode; 2use App\Models\Passport\Client; 3use App\Models\Passport\PersonalAccessClient; 4use App\Models\Passport\RefreshToken; 5use App\Models\Passport\Token; 6 7/** 8 * Bootstrap any application services. 9 */10public function boot(): void11{12 Passport::useTokenModel(Token::class);13 Passport::useRefreshTokenModel(RefreshToken::class);14 Passport::useAuthCodeModel(AuthCode::class);15 Passport::useClientModel(Client::class);16 Passport::usePersonalAccessClientModel(PersonalAccessClient::class);17}
Overriding Routes
有時您可能希望自訂 Passport 定義的路徑。若要達成此目的,您首先需要透過將 Passport::ignoreRoutes
新增至應用程式 AppServiceProvider
的 register
方法來忽略 Passport 註冊的路徑
1use Laravel\Passport\Passport;2 3/**4 * Register any application services.5 */6public function register(): void7{8 Passport::ignoreRoutes();9}
然後,您可以將 Passport 在 其路徑檔案 中定義的路徑複製到應用程式的 routes/web.php
檔案中,並根據您的喜好修改它們
1Route::group([2 'as' => 'passport.',3 'prefix' => config('passport.path', 'oauth'),4 'namespace' => '\Laravel\Passport\Http\Controllers',5], function () {6 // Passport routes...7});
Issuing Access Tokens
透過授權碼使用 OAuth2 是大多數開發人員熟悉的 OAuth2 使用方式。使用授權碼時,用戶端應用程式會將使用者重新導向到您的伺服器,他們將在其中批准或拒絕向用戶端發行存取令牌的請求。
Managing Clients
首先,建置需要與您的應用程式 API 互動的應用程式開發人員需要透過建立「用戶端」向您的應用程式註冊他們的應用程式。通常,這包括提供他們應用程式的名稱和一個 URL,您的應用程式可以在使用者批准他們的授權請求後重新導向到該 URL。
passport:client
命令
建立用戶端最簡單的方法是使用 passport:client
Artisan 命令。此命令可用於建立您自己的用戶端,以測試您的 OAuth2 功能。當您執行 client
命令時,Passport 會提示您提供有關用戶端的更多資訊,並為您提供用戶端 ID 和密鑰
1php artisan passport:client
重新導向 URL
如果您想允許您的用戶端使用多個重新導向 URL,您可以在 passport:client
命令提示您輸入 URL 時,使用逗號分隔的清單來指定它們。任何包含逗號的 URL 都應進行 URL 編碼
1http://example.com/callback,http://examplefoo.com/callback
JSON API
由於您的應用程式使用者將無法使用 client
命令,因此 Passport 提供了一個 JSON API,您可以使用它來建立用戶端。這省去了您必須手動編寫控制器來建立、更新和刪除用戶端的麻煩。
但是,您需要將 Passport 的 JSON API 與您自己的前端配對,以為您的使用者提供儀表板來管理他們的用戶端。在下面,我們將檢閱用於管理用戶端的所有 API 端點。為了方便起見,我們將使用 Axios 來示範向端點發出 HTTP 請求。
JSON API 受 web
和 auth
中介層保護;因此,它只能從您自己的應用程式呼叫。它無法從外部來源呼叫。
GET /oauth/clients
此路由傳回已驗證使用者的所有用戶端。這主要用於列出使用者的所有用戶端,以便他們可以編輯或刪除它們
1axios.get('/oauth/clients')2 .then(response => {3 console.log(response.data);4 });
POST /oauth/clients
此路由用於建立新的用戶端。它需要兩項資料:用戶端的 name
和一個 redirect
URL。redirect
URL 是使用者在批准或拒絕授權請求後將被重新導向到的位置。
建立用戶端時,將會發行用戶端 ID 和用戶端密鑰。這些值將在從您的應用程式請求存取令牌時使用。用戶端建立路由將傳回新的用戶端實例
1const data = { 2 name: 'Client Name', 3 redirect: 'http://example.com/callback' 4}; 5 6axios.post('/oauth/clients', data) 7 .then(response => { 8 console.log(response.data); 9 })10 .catch (response => {11 // List errors on response...12 });
PUT /oauth/clients/{client-id}
此路由用於更新用戶端。它需要兩項資料:用戶端的 name
和一個 redirect
URL。redirect
URL 是使用者在批准或拒絕授權請求後將被重新導向到的位置。該路由將傳回更新後的用戶端實例
1const data = { 2 name: 'New Client Name', 3 redirect: 'http://example.com/callback' 4}; 5 6axios.put('/oauth/clients/' + clientId, data) 7 .then(response => { 8 console.log(response.data); 9 })10 .catch (response => {11 // List errors on response...12 });
DELETE /oauth/clients/{client-id}
此路由用於刪除用戶端
1axios.delete('/oauth/clients/' + clientId)2 .then(response => {3 // ...4 });
Requesting Tokens
重新導向以進行授權
建立用戶端後,開發人員可以使用他們的用戶端 ID 和密鑰,從您的應用程式請求授權碼和存取令牌。首先,使用中的應用程式應向您的應用程式的 /oauth/authorize
路由發出重新導向請求,如下所示
1use Illuminate\Http\Request; 2use Illuminate\Support\Str; 3 4Route::get('/redirect', function (Request $request) { 5 $request->session()->put('state', $state = Str::random(40)); 6 7 $query = http_build_query([ 8 'client_id' => 'client-id', 9 'redirect_uri' => 'http://third-party-app.com/callback',10 'response_type' => 'code',11 'scope' => '',12 'state' => $state,13 // 'prompt' => '', // "none", "consent", or "login"14 ]);15 16 return redirect('http://passport-app.test/oauth/authorize?'.$query);17});
prompt
參數可用於指定 Passport 應用程式的身份驗證行為。
如果 prompt
值為 none
,則如果使用者尚未通過 Passport 應用程式的身份驗證,Passport 將始終拋出身份驗證錯誤。如果值為 consent
,即使之前已將所有範圍授予使用中的應用程式,Passport 也會始終顯示授權批准畫面。當值為 login
時,Passport 應用程式將始終提示使用者重新登入應用程式,即使他們已經有現有的工作階段。
如果未提供 prompt
值,則只有在使用者先前未授權使用中應用程式的請求範圍的存取權時,才會提示使用者進行授權。
請記住,/oauth/authorize
路由已由 Passport 定義。您無需手動定義此路由。
批准請求
接收授權請求時,Passport 將根據 prompt
參數的值(如果存在)自動回應,並且可能會向使用者顯示範本,允許他們批准或拒絕授權請求。如果他們批准請求,他們將被重新導向回使用中的應用程式指定的 redirect_uri
。redirect_uri
必須與建立用戶端時指定的 redirect
URL 相符。
如果您想自訂授權批准畫面,可以使用 vendor:publish
Artisan 命令發布 Passport 的視圖。發布的視圖將放置在 resources/views/vendor/passport
目錄中
1php artisan vendor:publish --tag=passport-views
有時您可能希望跳過授權提示,例如在授權第一方用戶端時。您可以透過擴充 Client
模型並定義 skipsAuthorization
方法來達成此目的。如果 skipsAuthorization
傳回 true
,則將批准用戶端,並且使用者將立即重新導向回 redirect_uri
,除非使用中的應用程式在重新導向以進行授權時明確設定了 prompt
參數
1<?php 2 3namespace App\Models\Passport; 4 5use Laravel\Passport\Client as BaseClient; 6 7class Client extends BaseClient 8{ 9 /**10 * Determine if the client should skip the authorization prompt.11 */12 public function skipsAuthorization(): bool13 {14 return $this->firstParty();15 }16}
將授權碼轉換為存取令牌
如果使用者批准授權請求,他們將被重新導向回使用中的應用程式。消費者應首先根據重新導向之前儲存的值驗證 state
參數。如果狀態參數相符,則消費者應向您的應用程式發出 POST
請求,以請求存取令牌。請求應包含您的應用程式在使用者批准授權請求時發行的授權碼
1use Illuminate\Http\Request; 2use Illuminate\Support\Facades\Http; 3 4Route::get('/callback', function (Request $request) { 5 $state = $request->session()->pull('state'); 6 7 throw_unless( 8 strlen($state) > 0 && $state === $request->state, 9 InvalidArgumentException::class,10 'Invalid state value.'11 );12 13 $response = Http::asForm()->post('http://passport-app.test/oauth/token', [14 'grant_type' => 'authorization_code',15 'client_id' => 'client-id',16 'client_secret' => 'client-secret',17 'redirect_uri' => 'http://third-party-app.com/callback',18 'code' => $request->code,19 ]);20 21 return $response->json();22});
此 /oauth/token
路由將傳回一個 JSON 回應,其中包含 access_token
、refresh_token
和 expires_in
屬性。expires_in
屬性包含存取令牌過期前的秒數。
與 /oauth/authorize
路由類似,/oauth/token
路由由 Passport 為您定義。無需手動定義此路由。
JSON API
Passport 還包含一個 JSON API,用於管理已授權的存取令牌。您可以將其與您自己的前端配對,為您的使用者提供一個儀表板來管理存取令牌。為了方便起見,我們將使用 Axios 來示範向端點發出 HTTP 請求。JSON API 受 web
和 auth
中介層保護;因此,它只能從您自己的應用程式呼叫。
GET /oauth/tokens
此路由傳回已驗證使用者建立的所有已授權存取令牌。這主要用於列出使用者的所有令牌,以便他們可以撤銷它們
1axios.get('/oauth/tokens')2 .then(response => {3 console.log(response.data);4 });
DELETE /oauth/tokens/{token-id}
此路由可用於撤銷已授權的存取令牌及其相關的重新整理令牌
1axios.delete('/oauth/tokens/' + tokenId);
Refreshing Tokens
如果您的應用程式發行短期存取令牌,則使用者需要透過在發行存取令牌時提供給他們的重新整理令牌來重新整理其存取令牌
1use Illuminate\Support\Facades\Http; 2 3$response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 4 'grant_type' => 'refresh_token', 5 'refresh_token' => 'the-refresh-token', 6 'client_id' => 'client-id', 7 'client_secret' => 'client-secret', 8 'scope' => '', 9]);10 11return $response->json();
此 /oauth/token
路由將傳回一個 JSON 回應,其中包含 access_token
、refresh_token
和 expires_in
屬性。expires_in
屬性包含存取令牌過期前的秒數。
Revoking Tokens
您可以使用 Laravel\Passport\TokenRepository
上的 revokeAccessToken
方法來撤銷令牌。您可以使用 Laravel\Passport\RefreshTokenRepository
上的 revokeRefreshTokensByAccessTokenId
方法來撤銷令牌的重新整理令牌。這些類別可以使用 Laravel 的 服務容器來解析
1use Laravel\Passport\TokenRepository; 2use Laravel\Passport\RefreshTokenRepository; 3 4$tokenRepository = app(TokenRepository::class); 5$refreshTokenRepository = app(RefreshTokenRepository::class); 6 7// Revoke an access token... 8$tokenRepository->revokeAccessToken($tokenId); 9 10// Revoke all of the token's refresh tokens...11$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);
Purging Tokens
當令牌被撤銷或過期時,您可能想要將它們從資料庫中清除。Passport 包含的 passport:purge
Artisan 命令可以為您執行此操作
1# Purge revoked and expired tokens and auth codes... 2php artisan passport:purge 3 4# Only purge tokens expired for more than 6 hours... 5php artisan passport:purge --hours=6 6 7# Only purge revoked tokens and auth codes... 8php artisan passport:purge --revoked 9 10# Only purge expired tokens and auth codes...11php artisan passport:purge --expired
您也可以在應用程式的 routes/console.php
檔案中設定一個排程任務,以排程自動修剪您的令牌
1use Illuminate\Support\Facades\Schedule;2 3Schedule::command('passport:purge')->hourly();
Authorization Code Grant With PKCE
使用「程式碼交換證明金鑰」(PKCE) 的授權碼授權是一種安全的方式,可用於驗證單頁應用程式或原生應用程式以存取您的 API。當您無法保證用戶端密鑰會被機密儲存,或者為了減輕授權碼被攻擊者攔截的威脅時,應使用此授權。當交換授權碼以取得存取令牌時,「程式碼驗證器」和「程式碼挑戰」的組合會取代用戶端密鑰。
Creating the Client
在您的應用程式可以透過使用 PKCE 的授權碼授權發行令牌之前,您需要建立一個啟用 PKCE 的用戶端。您可以使用帶有 --public
選項的 passport:client
Artisan 命令來執行此操作
1php artisan passport:client --public
Requesting Tokens
程式碼驗證器和程式碼挑戰
由於此授權授與不提供用戶端密鑰,因此開發人員需要產生程式碼驗證器和程式碼挑戰的組合才能請求令牌。
程式碼驗證器應為介於 43 到 128 個字元的隨機字串,其中包含字母、數字和 "-"
、"."
、"_"
、"~"
字元,如 RFC 7636 規範中所定義。
程式碼挑戰應為 Base64 編碼的字串,其中包含 URL 和檔案名稱安全字元。應移除尾隨的 '='
字元,並且不應存在換行符號、空格或其他額外字元。
1$encoded = base64_encode(hash('sha256', $code_verifier, true));2 3$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');
重新導向以進行授權
建立用戶端後,您可以使用用戶端 ID 以及產生的程式碼驗證器和程式碼挑戰,從您的應用程式請求授權碼和存取令牌。首先,使用中的應用程式應向您的應用程式的 /oauth/authorize
路由發出重新導向請求
1use Illuminate\Http\Request; 2use Illuminate\Support\Str; 3 4Route::get('/redirect', function (Request $request) { 5 $request->session()->put('state', $state = Str::random(40)); 6 7 $request->session()->put( 8 'code_verifier', $code_verifier = Str::random(128) 9 );10 11 $codeChallenge = strtr(rtrim(12 base64_encode(hash('sha256', $code_verifier, true))13 , '='), '+/', '-_');14 15 $query = http_build_query([16 'client_id' => 'client-id',17 'redirect_uri' => 'http://third-party-app.com/callback',18 'response_type' => 'code',19 'scope' => '',20 'state' => $state,21 'code_challenge' => $codeChallenge,22 'code_challenge_method' => 'S256',23 // 'prompt' => '', // "none", "consent", or "login"24 ]);25 26 return redirect('http://passport-app.test/oauth/authorize?'.$query);27});
將授權碼轉換為存取令牌
如果使用者批准授權請求,他們將被重新導向回使用中的應用程式。消費者應根據重新導向之前儲存的值驗證 state
參數,就像在標準授權碼授權中一樣。
如果狀態參數相符,則消費者應向您的應用程式發出 POST
請求,以請求存取令牌。請求應包含您的應用程式在使用者批准授權請求時發行的授權碼,以及原始產生的程式碼驗證器
1use Illuminate\Http\Request; 2use Illuminate\Support\Facades\Http; 3 4Route::get('/callback', function (Request $request) { 5 $state = $request->session()->pull('state'); 6 7 $codeVerifier = $request->session()->pull('code_verifier'); 8 9 throw_unless(10 strlen($state) > 0 && $state === $request->state,11 InvalidArgumentException::class12 );13 14 $response = Http::asForm()->post('http://passport-app.test/oauth/token', [15 'grant_type' => 'authorization_code',16 'client_id' => 'client-id',17 'redirect_uri' => 'http://third-party-app.com/callback',18 'code_verifier' => $codeVerifier,19 'code' => $request->code,20 ]);21 22 return $response->json();23});
Password Grant Tokens
我們不再建議使用密碼授與令牌。相反,您應該選擇OAuth2 Server 目前建議的授權類型。
OAuth2 密碼授與允許您的其他第一方用戶端(例如行動應用程式)使用電子郵件地址/使用者名稱和密碼來取得存取令牌。這使您可以安全地向您的第一方用戶端發行存取令牌,而無需您的使用者經歷整個 OAuth2 授權碼重新導向流程。
若要啟用密碼授與,請在應用程式 App\Providers\AppServiceProvider
類別的 boot
方法中呼叫 enablePasswordGrant
方法
1/**2 * Bootstrap any application services.3 */4public function boot(): void5{6 Passport::enablePasswordGrant();7}
Creating a Password Grant Client
在您的應用程式可以透過密碼授與發行令牌之前,您需要建立一個密碼授與用戶端。您可以使用帶有 --password
選項的 passport:client
Artisan 命令來執行此操作。如果您已執行 passport:install
命令,則無需執行此命令:
1php artisan passport:client --password
Requesting Tokens
建立密碼授與用戶端後,您可以透過向 /oauth/token
路由發出 POST
請求,並提供使用者的電子郵件地址和密碼來請求存取令牌。請記住,此路由已由 Passport 註冊,因此無需手動定義它。如果請求成功,您將在伺服器的 JSON 回應中收到 access_token
和 refresh_token
1use Illuminate\Support\Facades\Http; 2 3$response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 4 'grant_type' => 'password', 5 'client_id' => 'client-id', 6 'client_secret' => 'client-secret', 8 'password' => 'my-password', 9 'scope' => '',10]);11 12return $response->json();
請記住,預設情況下,存取令牌是長期有效的。但是,您可以自由地設定您的最大存取令牌生命週期(如果需要)。
Requesting All Scopes
當使用密碼授與或用戶端憑證授與時,您可能希望授權令牌使用您的應用程式支援的所有範圍。您可以透過請求 *
範圍來執行此操作。如果您請求 *
範圍,則令牌實例上的 can
方法將始終傳回 true
。此範圍只能指派給使用 password
或 client_credentials
授與發行的令牌
1use Illuminate\Support\Facades\Http; 2 3$response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 4 'grant_type' => 'password', 5 'client_id' => 'client-id', 6 'client_secret' => 'client-secret', 8 'password' => 'my-password', 9 'scope' => '*',10]);
Customizing the User Provider
如果您的應用程式使用多個身份驗證使用者提供器,您可以透過在使用 artisan passport:client --password
命令建立用戶端時提供 --provider
選項,來指定密碼授與用戶端使用的使用者提供器。給定的提供器名稱應與應用程式 config/auth.php
設定檔中定義的有效提供器相符。然後,您可以使用中介層保護您的路由,以確保只有來自守衛指定提供器的使用者獲得授權。
Customizing the Username Field
當使用密碼授與進行身份驗證時,Passport 將使用您的可身份驗證模型的 email
屬性作為「使用者名稱」。但是,您可以透過在模型上定義 findForPassport
方法來自訂此行為
1<?php 2 3namespace App\Models; 4 5use Illuminate\Foundation\Auth\User as Authenticatable; 6use Illuminate\Notifications\Notifiable; 7use Laravel\Passport\HasApiTokens; 8 9class User extends Authenticatable10{11 use HasApiTokens, Notifiable;12 13 /**14 * Find the user instance for the given username.15 */16 public function findForPassport(string $username): User17 {18 return $this->where('username', $username)->first();19 }20}
Customizing the Password Validation
當使用密碼授與進行身份驗證時,Passport 將使用您模型的 password
屬性來驗證給定的密碼。如果您的模型沒有 password
屬性,或者您希望自訂密碼驗證邏輯,您可以在模型上定義 validateForPassportPasswordGrant
方法
1<?php 2 3namespace App\Models; 4 5use Illuminate\Foundation\Auth\User as Authenticatable; 6use Illuminate\Notifications\Notifiable; 7use Illuminate\Support\Facades\Hash; 8use Laravel\Passport\HasApiTokens; 9 10class User extends Authenticatable11{12 use HasApiTokens, Notifiable;13 14 /**15 * Validate the password of the user for the Passport password grant.16 */17 public function validateForPassportPasswordGrant(string $password): bool18 {19 return Hash::check($password, $this->password);20 }21}
Implicit Grant Tokens
我們不再建議使用隱式授與令牌。相反,您應該選擇OAuth2 Server 目前建議的授權類型。
隱式授與與授權碼授與類似;但是,令牌會傳回給用戶端,而無需交換授權碼。此授與最常用於無法安全儲存用戶端憑證的 JavaScript 或行動應用程式。若要啟用授與,請在應用程式 App\Providers\AppServiceProvider
類別的 boot
方法中呼叫 enableImplicitGrant
方法
1/**2 * Bootstrap any application services.3 */4public function boot(): void5{6 Passport::enableImplicitGrant();7}
啟用授與後,開發人員可以使用他們的用戶端 ID 從您的應用程式請求存取令牌。使用中的應用程式應向您的應用程式的 /oauth/authorize
路由發出重新導向請求,如下所示
1use Illuminate\Http\Request; 2 3Route::get('/redirect', function (Request $request) { 4 $request->session()->put('state', $state = Str::random(40)); 5 6 $query = http_build_query([ 7 'client_id' => 'client-id', 8 'redirect_uri' => 'http://third-party-app.com/callback', 9 'response_type' => 'token',10 'scope' => '',11 'state' => $state,12 // 'prompt' => '', // "none", "consent", or "login"13 ]);14 15 return redirect('http://passport-app.test/oauth/authorize?'.$query);16});
請記住,/oauth/authorize
路由已由 Passport 定義。您無需手動定義此路由。
Client Credentials Grant Tokens
用戶端憑證授與適用於機器對機器身份驗證。例如,您可能會在排程任務中使用此授與,該任務透過 API 執行維護工作。
在您的應用程式可以透過用戶端憑證授與發行令牌之前,您需要建立一個用戶端憑證授與用戶端。您可以使用 passport:client
Artisan 命令的 --client
選項來執行此操作
1php artisan passport:client --client
接下來,若要使用此授與類型,請為 CheckClientCredentials
中介層註冊中介層別名。您可以在應用程式的 bootstrap/app.php
檔案中定義中介層別名
1use Laravel\Passport\Http\Middleware\CheckClientCredentials;2 3->withMiddleware(function (Middleware $middleware) {4 $middleware->alias([5 'client' => CheckClientCredentials::class6 ]);7})
然後,將中介層附加到路由
1Route::get('/orders', function (Request $request) {2 // ...3})->middleware('client');
若要將路由的存取權限制為特定範圍,您可以在將 client
中介層附加到路由時,提供以逗號分隔的所需範圍清單
1Route::get('/orders', function (Request $request) {2 // ...3})->middleware('client:check-status,your-scope');
擷取令牌
若要使用此授與類型擷取令牌,請向 oauth/token
端點發出請求
1use Illuminate\Support\Facades\Http; 2 3$response = Http::asForm()->post('http://passport-app.test/oauth/token', [ 4 'grant_type' => 'client_credentials', 5 'client_id' => 'client-id', 6 'client_secret' => 'client-secret', 7 'scope' => 'your-scope', 8]); 9 10return $response->json()['access_token'];
Personal Access Tokens
有時,您的使用者可能想要自行發行存取令牌,而無需經歷典型的授權碼重新導向流程。允許使用者透過您的應用程式 UI 自行發行令牌,對於允許使用者試用您的 API 或作為一般發行存取令牌的更簡單方法可能很有用。
如果您的應用程式主要使用 Passport 來發行個人存取令牌,請考慮使用 Laravel Sanctum,Laravel 的輕量級第一方程式庫,用於發行 API 存取令牌。
Creating a Personal Access Client
在您的應用程式可以發行個人存取令牌之前,您需要建立一個個人存取用戶端。您可以透過執行帶有 --personal
選項的 passport:client
Artisan 命令來執行此操作。如果您已執行 passport:install
命令,則無需執行此命令
1php artisan passport:client --personal
建立個人存取用戶端後,將用戶端的 ID 和純文字密鑰值放在應用程式的 .env
檔案中
1PASSPORT_PERSONAL_ACCESS_CLIENT_ID="client-id-value"2PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET="unhashed-client-secret-value"
Managing Personal Access Tokens
建立個人存取用戶端後,您可以使用 App\Models\User
模型實例上的 createToken
方法為給定使用者發行令牌。createToken
方法接受令牌名稱作為其第一個引數,並接受 範圍的可選陣列作為其第二個引數
1use App\Models\User;2 3$user = User::find(1);4 5// Creating a token without scopes...6$token = $user->createToken('Token Name')->accessToken;7 8// Creating a token with scopes...9$token = $user->createToken('My Token', ['place-orders'])->accessToken;
JSON API
Passport 還包含一個 JSON API,用於管理個人存取令牌。您可以將其與您自己的前端配對,為您的使用者提供一個儀表板來管理個人存取令牌。在下面,我們將檢閱用於管理個人存取令牌的所有 API 端點。為了方便起見,我們將使用 Axios 來示範向端點發出 HTTP 請求。
JSON API 受 web
和 auth
中介層保護;因此,它只能從您自己的應用程式呼叫。它無法從外部來源呼叫。
GET /oauth/scopes
此路由會傳回為您的應用程式定義的所有 scopes(權限範圍)。您可以使用此路由來列出使用者可以指派給個人存取權杖的 scopes。
1axios.get('/oauth/scopes')2 .then(response => {3 console.log(response.data);4 });
GET /oauth/personal-access-tokens
此路由會傳回經過身份驗證的使用者所建立的所有個人存取權杖。這主要用於列出使用者的所有權杖,以便他們可以編輯或撤銷這些權杖。
1axios.get('/oauth/personal-access-tokens')2 .then(response => {3 console.log(response.data);4 });
POST /oauth/personal-access-tokens
此路由會建立新的個人存取權杖。它需要兩項資料:權杖的 name
和應指派給權杖的 scopes
。
1const data = { 2 name: 'Token Name', 3 scopes: [] 4}; 5 6axios.post('/oauth/personal-access-tokens', data) 7 .then(response => { 8 console.log(response.data.accessToken); 9 })10 .catch (response => {11 // List errors on response...12 });
DELETE /oauth/personal-access-tokens/{token-id}
此路由可用於撤銷個人存取權杖。
1axios.delete('/oauth/personal-access-tokens/' + tokenId);
Protecting Routes
Via Middleware
Passport 包含一個 身份驗證守衛 (authentication guard),它將驗證傳入請求的存取權杖。一旦您將 api
守衛配置為使用 passport
驅動程式,您只需要在任何需要有效存取權杖的路徑上指定 auth:api
中介層。
1Route::get('/user', function () {2 // ...3})->middleware('auth:api');
如果您正在使用 用戶端憑證授權 (client credentials grant),則應使用 client
中介層 來保護您的路由,而不是 auth:api
中介層。
多個身份驗證守衛
如果您的應用程式驗證不同類型的使用者(他們可能使用完全不同的 Eloquent 模型),您可能需要在您的應用程式中為每種使用者提供者類型定義一個守衛配置。這允許您保護針對特定使用者提供者的請求。例如,假設 config/auth.php
配置檔案中有以下守衛配置:
1'api' => [2 'driver' => 'passport',3 'provider' => 'users',4],5 6'api-customers' => [7 'driver' => 'passport',8 'provider' => 'customers',9],
以下路由將使用 api-customers
守衛(它使用 customers
使用者提供者)來驗證傳入的請求。
1Route::get('/customer', function () {2 // ...3})->middleware('auth:api-customers');
有關使用 Passport 與多個使用者提供者的更多資訊,請參閱 密碼授權文件。
Passing the Access Token
當呼叫受 Passport 保護的路由時,您應用程式的 API 消費者應在其請求的 Authorization
標頭中將其存取權杖指定為 Bearer
權杖。例如,當使用 Guzzle HTTP 函式庫時:
1use Illuminate\Support\Facades\Http;2 3$response = Http::withHeaders([4 'Accept' => 'application/json',5 'Authorization' => 'Bearer '.$accessToken,6])->get('https://passport-app.test/api/user');7 8return $response->json();
Token Scopes
Scopes(權限範圍)允許您的 API 用戶端在請求授權以存取帳戶時,請求一組特定的權限。例如,如果您正在建置電子商務應用程式,並非所有 API 消費者都需要下訂單的能力。相反地,您可以允許消費者僅請求授權以存取訂單出貨狀態。換句話說,Scopes 允許您應用程式的使用者限制第三方應用程式可以代表他們執行的操作。
Defining Scopes
您可以使用應用程式 App\Providers\AppServiceProvider
類別的 boot
方法中的 Passport::tokensCan
方法來定義 API 的 scopes(權限範圍)。tokensCan
方法接受 scope 名稱和 scope 描述的陣列。scope 描述可以是您想要的任何內容,並將在授權核准畫面上向使用者顯示。
1/** 2 * Bootstrap any application services. 3 */ 4public function boot(): void 5{ 6 Passport::tokensCan([ 7 'place-orders' => 'Place orders', 8 'check-status' => 'Check order status', 9 ]);10}
Default Scope
如果用戶端未請求任何特定 scopes(權限範圍),您可以配置您的 Passport 伺服器使用 setDefaultScope
方法將預設 scope (s) 附加到權杖。通常,您應該從應用程式 App\Providers\AppServiceProvider
類別的 boot
方法中呼叫此方法。
1use Laravel\Passport\Passport; 2 3Passport::tokensCan([ 4 'place-orders' => 'Place orders', 5 'check-status' => 'Check order status', 6]); 7 8Passport::setDefaultScope([ 9 'check-status',10 'place-orders',11]);
Passport 的預設 scopes(權限範圍)不適用於使用者產生的個人存取權杖。
Assigning Scopes to Tokens
當請求授權碼時
當使用授權碼授權 (authorization code grant) 請求存取權杖時,消費者應將他們想要的 scopes(權限範圍)指定為 scope
查詢字串參數。scope
參數應為以空格分隔的 scopes 列表。
1Route::get('/redirect', function () { 2 $query = http_build_query([ 3 'client_id' => 'client-id', 4 'redirect_uri' => 'http://example.com/callback', 5 'response_type' => 'code', 6 'scope' => 'place-orders check-status', 7 ]); 8 9 return redirect('http://passport-app.test/oauth/authorize?'.$query);10});
當發行個人存取權杖時
如果您正在使用 App\Models\User
模型的 createToken
方法發行個人存取權杖,您可以將所需 scopes(權限範圍)的陣列作為第二個引數傳遞給該方法。
1$token = $user->createToken('My Token', ['place-orders'])->accessToken;
Checking Scopes
Passport 包含兩個中介層,可用於驗證傳入的請求是否已使用已被授予給定 scope(權限範圍)的權杖進行身份驗證。若要開始使用,請在您應用程式的 bootstrap/app.php
檔案中定義以下中介層別名:
1use Laravel\Passport\Http\Middleware\CheckForAnyScope;2use Laravel\Passport\Http\Middleware\CheckScopes;3 4->withMiddleware(function (Middleware $middleware) {5 $middleware->alias([6 'scopes' => CheckScopes::class,7 'scope' => CheckForAnyScope::class,8 ]);9})
檢查所有 Scopes
scopes
中介層可以指派給路由,以驗證傳入請求的存取權杖是否具有所有列出的 scopes(權限範圍)。
1Route::get('/orders', function () {2 // Access token has both "check-status" and "place-orders" scopes...3})->middleware(['auth:api', 'scopes:check-status,place-orders']);
檢查任一 Scope
scope
中介層可以指派給路由,以驗證傳入請求的存取權杖是否具有至少一個列出的 scopes(權限範圍)。
1Route::get('/orders', function () {2 // Access token has either "check-status" or "place-orders" scope...3})->middleware(['auth:api', 'scope:check-status,place-orders']);
檢查權杖實例上的 Scopes
一旦存取權杖驗證的請求已進入您的應用程式,您仍然可以使用經過身份驗證的 App\Models\User
實例上的 tokenCan
方法來檢查權杖是否具有給定的 scope(權限範圍)。
1use Illuminate\Http\Request;2 3Route::get('/orders', function (Request $request) {4 if ($request->user()->tokenCan('place-orders')) {5 // ...6 }7});
其他 Scope 方法
scopeIds
方法將傳回所有已定義 ID / 名稱的陣列。
1use Laravel\Passport\Passport;2 3Passport::scopeIds();
scopes
方法將傳回所有已定義 scopes(權限範圍)的陣列,作為 Laravel\Passport\Scope
的實例。
1Passport::scopes();
scopesFor
方法將傳回與給定 ID / 名稱匹配的 Laravel\Passport\Scope
實例的陣列。
1Passport::scopesFor(['place-orders', 'check-status']);
您可以使用 hasScope
方法來判斷是否已定義給定的 scope(權限範圍)。
1Passport::hasScope('place-orders');
Consuming Your API With JavaScript
當建置 API 時,能夠從您的 JavaScript 應用程式使用自己的 API 可能非常有用。這種 API 開發方法允許您自己的應用程式使用與您與世界分享的相同的 API。相同的 API 可以被您的 Web 應用程式、行動應用程式、第三方應用程式以及您可能在各種套件管理器上發布的任何 SDK 使用。
通常,如果您想從您的 JavaScript 應用程式使用您的 API,您需要手動將存取權杖傳送到應用程式,並在每次向您的應用程式發出請求時傳遞它。但是,Passport 包含一個中介層可以為您處理此問題。您所需要做的只是將 CreateFreshApiToken
中介層附加到您應用程式 bootstrap/app.php
檔案中的 web
中介層群組。
1use Laravel\Passport\Http\Middleware\CreateFreshApiToken;2 3->withMiddleware(function (Middleware $middleware) {4 $middleware->web(append: [5 CreateFreshApiToken::class,6 ]);7})
您應確保 CreateFreshApiToken
中介層是您中介層堆疊中列出的最後一個中介層。
此中介層會將 laravel_token
Cookie 附加到您的傳出回應。此 Cookie 包含一個加密的 JWT,Passport 將使用它來驗證來自您的 JavaScript 應用程式的 API 請求。JWT 的生命週期等於您的 session.lifetime
配置值。現在,由於瀏覽器將自動將 Cookie 與所有後續請求一起傳送,因此您可以向您應用程式的 API 發出請求,而無需明確傳遞存取權杖。
1axios.get('/api/user')2 .then(response => {3 console.log(response.data);4 });
自訂 Cookie 名稱
如果需要,您可以使用 Passport::cookie
方法自訂 laravel_token
Cookie 的名稱。通常,應從應用程式 App\Providers\AppServiceProvider
類別的 boot
方法中呼叫此方法。
1/**2 * Bootstrap any application services.3 */4public function boot(): void5{6 Passport::cookie('custom_name');7}
CSRF Protection
當使用此身份驗證方法時,您需要確保您的請求中包含有效的 CSRF 權杖標頭。預設的 Laravel JavaScript scaffolding 包含一個 Axios 實例,它將自動使用加密的 XSRF-TOKEN
Cookie 值在同源請求上傳送 X-XSRF-TOKEN
標頭。
如果您選擇傳送 X-CSRF-TOKEN
標頭而不是 X-XSRF-TOKEN
,您將需要使用 csrf_token()
提供的未加密權杖。
Events
Passport 在發行存取權杖和重新整理權杖時會引發事件。您可以監聽這些事件,以修剪或撤銷資料庫中的其他存取權杖。
事件名稱 |
---|
Laravel\Passport\Events\AccessTokenCreated |
Laravel\Passport\Events\RefreshTokenCreated |
Testing
Passport 的 actingAs
方法可用於指定目前經過身份驗證的使用者及其 scopes(權限範圍)。提供給 actingAs
方法的第一個引數是使用者實例,第二個是應授予使用者權杖的 scopes 陣列。
1use App\Models\User; 2use Laravel\Passport\Passport; 3 4test('servers can be created', function () { 5 Passport::actingAs( 6 User::factory()->create(), 7 ['create-servers'] 8 ); 9 10 $response = $this->post('/api/create-server');11 12 $response->assertStatus(201);13});
1use App\Models\User; 2use Laravel\Passport\Passport; 3 4public function test_servers_can_be_created(): void 5{ 6 Passport::actingAs( 7 User::factory()->create(), 8 ['create-servers'] 9 );10 11 $response = $this->post('/api/create-server');12 13 $response->assertStatus(201);14}
Passport 的 actingAsClient
方法可用於指定目前經過身份驗證的用戶端及其 scopes(權限範圍)。提供給 actingAsClient
方法的第一個引數是用戶端實例,第二個是應授予用戶端權杖的 scopes 陣列。
1use Laravel\Passport\Client; 2use Laravel\Passport\Passport; 3 4test('orders can be retrieved', function () { 5 Passport::actingAsClient( 6 Client::factory()->create(), 7 ['check-status'] 8 ); 9 10 $response = $this->get('/api/orders');11 12 $response->assertStatus(200);13});
1use Laravel\Passport\Client; 2use Laravel\Passport\Passport; 3 4public function test_orders_can_be_retrieved(): void 5{ 6 Passport::actingAsClient( 7 Client::factory()->create(), 8 ['check-status'] 9 );10 11 $response = $this->get('/api/orders');12 13 $response->assertStatus(200);14}