Laravel Passport cung cấp một implementation OAuth2 server đầy đủ cho application Laravel của bạn trong vài phút. Passport được xây dựng trên top của League OAuth2 server được duy trì bởi Andy Millington và Simon Hamp.
{note} Tài liệu này giả định rằng bạn đã biết OAuth2. Nếu bạn chưa biết về OAuth2, hãy xem xét việc tự học với các thuật ngữ và tính năng chung của OAuth2 trước khi tiếp tục.
Trước khi bắt đầu, bạn có thể muốn xem xét xem ứng dụng của bạn sẽ được phục vụ tốt hơn bởi Laravel Passport hay Laravel Sanctum. Nếu ứng dụng của bạn thực sự cần hỗ trợ OAuth2 thì bạn nên sử dụng Laravel Passport.
Tuy nhiên, nếu bạn đang làm xác thực cho một ứng dụng single-page, mobile application hoặc phát hành API token, bạn nên sử dụng Laravel Sanctum. Laravel Sanctum không hỗ trợ OAuth2; tuy nhiên, nó cung cấp trải nghiệm phát triển xác thực API đơn giản hơn nhiều.
Để bắt đầu, hãy cài đặt Passport thông qua Composer package manager:
composer require laravel/passport
Service provider của Passport sẽ đăng ký thư mục database migration của riêng nó với framework, nên vì thế bạn nên migrate cơ sở dữ liệu của bạn sau khi cài đặt xong package. Việc migrate của Passport sẽ tạo ra các table mà application của bạn cần để lưu trữ OAuth2 client và access token:
php artisan migrate
Tiếp theo, bạn nên chạy lệnh Artisan passport:install
. Lệnh này sẽ tạo các key mã hóa cần thiết để tạo secure access token. Ngoài ra, lệnh này cũng sẽ tạo các "personal access" và các "password grant" client được sử dụng để tạo access token:
php artisan passport:install
{tip} Nếu bạn muốn sử dụng UUID làm khóa chính của model Passport
Client
thay vì các integer tự động tăng, vui lòng cài đặt Passport với tùy chọnuuids
.
Sau khi chạy lệnh passport:install
, hãy thêm trait Laravel\Passport\HasApiTokens
vào model App\User
của bạn. Trait này sẽ cung cấp một vài phương thức helper cho model của bạn, cho phép bạn kiểm tra token và phạm vi của người dùng đã được authenticate. Nếu model của bạn đã sử dụng trait Laravel\Sanctum\HasApiTokens
, bạn có thể xóa trait đó đi:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
Tiếp theo, bạn nên gọi phương thức Passport::routes
vào trong phương thức boot
của App\Providers\AuthServiceProvider
của bạn. Phương thức này sẽ đăng ký các route cần thiết để phát hành các access token và thu hồi các access token, client và các access token cá nhân:
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Laravel\Passport\Passport;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
if (! $this->app->routesAreCached()) {
Passport::routes();
}
}
}
Cuối cùng, trong file cấu hình config/auth.php
của application của bạn, bạn nên set tùy chọn driver
của api
authentication guard thành passport
. Điều này sẽ hướng dẫn application của bạn sử dụng TokenGuard
của Passport khi authenticate các request API:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
Bạn cũng có thể chạy lệnh passport:install
với tùy chọn --uuids
. Tuỳ chọn này sẽ hướng dẫn Passport là bạn muốn sử dụng UUID làm giá trị khóa chính của model Passport Client
thay vì một integer tự động tăng. Sau khi chạy lệnh passport:install
với tùy chọn --uuids
, bạn cũng sẽ được nhận được các hướng dẫn bổ sung về cách tắt tính năng migration mặc định của Passport:
php artisan passport:install --uuids
Khi deploy Passport lần đầu đến server application của bạn, bạn có thể sẽ cần chạy lệnh passport:keys
. Lệnh này sẽ tạo các key mã hóa Passport cần, để tạo access token. Các key được tạo thường không nên được lưu trữ trong source code control:
php artisan passport:keys
Nếu cần, bạn có thể định nghĩa đường dẫn nơi mà các khóa của Passport sẽ được load từ đó. Bạn có thể sử dụng phương thức Passport::loadKeysFrom
để thực hiện việc này. Thông thường, phương thức này phải được gọi từ phương thức boot
của class App\Providers\AuthServiceProvider
trong ứng dụng của bạn:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');
}
Ngoài ra, bạn có thể export file cấu hình của Passport bằng lệnh Artisan vendor:publish
:
php artisan vendor:publish --tag=passport-config
Sau khi file cấu hình được export, bạn có thể load khóa mã hóa của ứng dụng bằng cách định nghĩa chúng dưới dạng biến môi trường:
PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
<private key here>
-----END RSA PRIVATE KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
<public key here>
-----END PUBLIC KEY-----"
Nếu bạn không muốn sử dụng migration mặc định của Passport, bạn nên gọi phương thức Passport::ignoreMigrations
trong phương thức register
của class App\Providers\AppServiceProvider
của bạn. Bạn có thể export các migration mặc định này bằng cách sử dụng lệnh Artisan vendor:publish
:
php artisan vendor:publish --tag=passport-migrations
Khi nâng cấp lên phiên bản mới của Passport, điều quan trọng là bạn phải xem kỹ hướng dẫn nâng cấp.
Nếu bạn muốn hash các client secret khi lưu vào trong cơ sở dữ liệu của bạn, bạn nên gọi phương thức Passport::hashClientSecrets
trong phương thức boot
của class App\Providers\AuthServiceProvider
:
use Laravel\Passport\Passport;
Passport::hashClientSecrets();
Sau khi bạn đã cài đặt xong, tất cả các client secret của bạn sẽ chỉ có thể hiển thị cho người dùng ngay sau khi họ tạo ra. Vì giá trị chính xác của client secret này sẽ không bao giờ được lưu vào trong cơ sở dữ liệu, nên bạn sẽ không thể khôi phục lại giá trị của secret nếu nó bị mất.
Mặc định, Passport phát hành các access token tồn tại lâu dài có thời hạn một năm. Nếu bạn muốn cấu hình vòng đời token dài hoặc ngắn hơn, bạn có thể sử dụng các phương thức tokensExpireIn
, refreshTokensExpireIn
, và personalAccessTokensExpireIn
. Các phương thức này phải được gọi từ phương thức boot
của class App\Providers\AuthServiceProvider
của application:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}
{note} Các cột
expires_at
trong bảng cơ sở dữ liệu Passport sẽ ở chế độ chỉ-đọc và chỉ dành cho mục đích hiển thị. Khi phát hành token, Passport sẽ lưu trữ thông tin hết hạn vào trong các token đó và mã hóa chúng. Nếu bạn muốn làm mất hiệu lực token, bạn nên thu hồi nó.
Bạn có thể thoải mái mở rộng các model được sử dụng trong nội bộ Passport bằng cách định nghĩa model của riêng bạn và extend model Passport tương ứng:
use Laravel\Passport\Client as PassportClient;
class Client extends PassportClient
{
// ...
}
Sau khi định nghĩa xong model của bạn, bạn có thể hướng dẫn Passport sử dụng các model tùy biến này thông qua Laravel\Passport\Passport
class. Thông thường, bạn nên thông báo cho Passport biết về các model tùy chỉnh của bạn trong phương thức boot
của class App\Providers\AuthServiceProvider
trong ứng dụng của bạn:
use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\PersonalAccessClient;
use App\Models\Passport\Token;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::useTokenModel(Token::class);
Passport::useClientModel(Client::class);
Passport::useAuthCodeModel(AuthCode::class);
Passport::usePersonalAccessClientModel(PersonalAccessClient::class);
}
Sử dụng OAuth2 thông qua authorization code là cách mà hầu hết các nhà phát triển quen thuộc với OAuth2. Khi sử dụng authorization code, một client application sẽ chuyển hướng người dùng đến server của bạn, nơi mà người dùng sẽ chấp nhận hoặc từ chối request cấp access token cho client.
Đầu tiên, các nhà phát triển cần xây dựng các ứng dụng của họ, mà cần tương tác với API application của bạn, họ sẽ cần phải đăng ký ứng dụng của họ với application của bạn bằng cách tạo "client". Thông thường, việc này bao gồm việc cung cấp tên ứng dụng và URL mà application của bạn cần chuyển hướng người dùng đến sau khi người dùng đã chấp thuận request ủy quyền.
passport:client
Cách đơn giản nhất để tạo một client là sử dụng lệnh Artisan passport:client
. Lệnh này có thể được sử dụng để tạo các client của riêng bạn để test các chức năng OAuth2. Khi bạn chạy lệnh client
, Passport sẽ hỏi bạn cho biết thêm thông tin về client của bạn và trả về cho bạn một client ID và một secret:
php artisan passport:client
Redirect URLs
Nếu bạn muốn lập một danh sách cho phép nhiều URL chuyển hướng cho client của bạn, bạn có thể chỉ định chúng bằng cách sử dụng một danh sách được phân cách bằng dấu phẩy khi nhập URL bằng lệnh passport:client
. Bất kỳ URL nào chứa dấu phẩy đều phải được encode URL:
http://example.com/callback,http://examplefoo.com/callback
Vì người dùng application của bạn sẽ không thể sử dụng lệnh client
, nên Passport cũng cung cấp một JSON API mà bạn có thể sử dụng để tạo client. Điều này giúp bạn tránh những rắc rối khi phải tự viết controller để tạo, cập nhật và xóa client.
Tuy nhiên, bạn sẽ cần kết nối JSON API của Passport với frontend của bạn để cung cấp một bảng điều khiển cho người dùng biết và quản lý các client của họ. Dưới đây, chúng ta sẽ xem xét tất cả các API endpoint để quản lý client. Để thuận tiện, chúng ta sẽ sử dụng Axios để thực hiện các HTTP request đến các endpoint.
JSON API được bảo vệ bởi middleware web
và auth
; do đó, nó chỉ có thể được gọi từ ứng dụng của bạn. Nó không thể được gọi từ một nguồn ở bên ngoài nào khác.
GET /oauth/clients
Route này sẽ trả về tất cả các client cho người dùng đã được authenticate. Điều này chủ yếu hữu ích để liệt kê tất cả các client của người dùng để họ có thể chỉnh sửa hoặc xóa chúng:
axios.get('/oauth/clients')
.then(response => {
console.log(response.data);
});
POST /oauth/clients
Route này được sử dụng để tạo client mới. Nó đòi hỏi hai phần dữ liệu: một là name
của client và một URL redirect
. URL redirect
là nơi người dùng sẽ được chuyển hướng đến sau khi chấp nhận hoặc từ chối một request cho authorization.
Sau Khi một client đã được tạo, nó sẽ được cũng cấp cho một client ID và một client secret. Các giá trị này sẽ được sử dụng khi yêu cầu access token từ application của bạn. Route tạo client sẽ trả về instance client mới:
const data = {
name: 'Client Name',
redirect: 'http://example.com/callback'
};
axios.post('/oauth/clients', data)
.then(response => {
console.log(response.data);
})
.catch (response => {
// List errors on response...
});
PUT /oauth/clients/{client-id}
Route này được sử dụng để cập nhật client. Nó đòi hỏi hai phần dữ liệu: một là name
của client và một URL redirect
. URL redirect
là nơi người dùng sẽ được chuyển hướng đến sau khi chấp nhận hoặc từ chối một request cho authorization. Route sẽ trả về instance client đã được cập nhật:
const data = {
name: 'New Client Name',
redirect: 'http://example.com/callback'
};
axios.put('/oauth/clients/' + clientId, data)
.then(response => {
console.log(response.data);
})
.catch (response => {
// List errors on response...
});
DELETE /oauth/clients/{client-id}
Route này được sử dụng để xóa client:
axios.delete('/oauth/clients/' + clientId)
.then(response => {
//
});
Khi một client đã được tạo, các developer có thể sử dụng client ID và secret được trả về để yêu cầu authorization code và access token từ application của bạn. Đầu tiên, application của bên thứ ba sẽ tạo một yêu cầu chuyển hướng đến route /oauth/authorize
của application của bạn như sau:
use Illuminate\Http\Request;
use Illuminate\Support\Str;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$query = http_build_query([
'client_id' => 'client-id',
'redirect_uri' => 'http://third-party-app.com/callback',
'response_type' => 'code',
'scope' => '',
'state' => $state,
]);
return redirect('http://passport-app.test/oauth/authorize?'.$query);
});
{tip} Hãy nhớ rằng, route
/oauth/authorize
đã được định nghĩa bởi phương thứcPassport::routes
. Bạn không cần phải tự định nghĩa route này.
Khi nhận được authorization request, Passport sẽ tự động hiển thị một template cho người dùng để họ có thể chấp nhận hoặc từ chối authorization request. Nếu họ chấp nhận request, họ sẽ được chuyển hướng trở lại redirect_uri
sẽ được chỉ định bởi application của bên thứ ba. redirect_uri
phải khớp với URL redirect
được chỉ định khi client được tạo.
Nếu bạn muốn tùy chỉnh màn hình phê duyệt authorization, bạn có thể publish các view của Passport bằng cách sử dụng lệnh Artisan vendor:publish
. Các view được publish sẽ được lưu trong thư mục resources/views/vendor/passport
:
php artisan vendor:publish --tag=passport-views
Thỉnh thoảng bạn có thể muốn bỏ qua các lời nhắc cấp quyền, chẳng hạn như khi cấp quyền cho client bên thứ nhất. Bạn có thể thực hiện điều này bằng cách extending the Client
model và định nghĩa phương thức skipsAuthorization
. Nếu skipsAuthorization
trả về true
thì ứng dụng client sẽ được chấp thuận và người dùng sẽ được chuyển hướng trở lại về redirect_uri
ngay lập tức:
<?php
namespace App\Models\Passport;
use Laravel\Passport\Client as BaseClient;
class Client extends BaseClient
{
/**
* Determine if the client should skip the authorization prompt.
*
* @return bool
*/
public function skipsAuthorization()
{
return $this->firstParty();
}
}
Nếu người dùng chấp nhận authorization request, họ sẽ được chuyển hướng trở lại application của bên thứ ba. Sau đó, đầu tiên, bên thứ ba sẽ kiểm tra tham số state
với giá trị đã được lưu trữ trước khi chuyển hướng. Nếu tham số state trùng khớp với giá trị đã được lưu, nó sẽ đưa ra một request POST
cho application của bạn để yêu cầu access token. Yêu cầu phải chứa authorization code được cấp bởi application của bạn khi người dùng chấp nhận authorization request. Trong ví dụ này, chúng ta sẽ sử dụng thư viện Guzzle HTTP để thực hiện request POST
:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
Route::get('/callback', function (Request $request) {
$state = $request->session()->pull('state');
throw_unless(
strlen($state) > 0 && $state === $request->state,
InvalidArgumentException::class
);
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'redirect_uri' => 'http://third-party-app.com/callback',
'code' => $request->code,
]);
return $response->json();
});
Route /oauth/token
này sẽ trả về một JSON response có chứa các thuộc tính access_token
, refresh_token
và expires_in
. Thuộc tính expires_in
sẽ chứa số giây cho đến khi access token hết hạn.
{tip} Giống như route
/oauth/authorize
, route/oauth/token
đã được định nghĩa cho bạn bằng phương thứcPassport::routes
. Bạn không cần phải tự định nghĩa route này.
Passport cũng chứa một JSON API để quản lý các access token đã được ủy quyền. Bạn có thể ghép API này với giao diện người dùng của riêng bạn để cung cấp cho người dùng một trang tổng thể để quản lý access token. Để thuận tiện, chúng ta sẽ sử dụng Axios để demo việc thực hiện các HTTP request tới các endpoint. JSON API này được bảo vệ bởi middleware web
và auth
; do đó, nó chỉ có thể được gọi từ ứng dụng của bạn.
GET /oauth/tokens
Route này sẽ trả về tất cả các access token đã được ủy quyền mà người dùng đã tạo. Điều này chủ yếu hữu ích cho việc hiển thị tất cả các token của người dùng để họ có thể thu hồi chúng:
axios.get('/oauth/tokens')
.then(response => {
console.log(response.data);
});
DELETE /oauth/tokens/{token-id}
Route này có thể được sử dụng để thu hồi một access token đã được ủy quyền và các refresh token liên quan của chúng:
axios.delete('/oauth/tokens/' + tokenId);
Nếu application của bạn phát hành access token ngắn hạn, người dùng sẽ cần phải refresh access token của họ thông qua refresh token được cung cấp cho họ khi access token được phát hành:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => '',
]);
return $response->json();
Route /oauth/token
này sẽ trả về một JSON response có chứa các thuộc tính access_token
, refresh_token
và expires_in
. Thuộc tính expires_in
sẽ chứa số giây cho đến khi access token hết hạn.
Bạn có thể thu hồi một token cách sử dụng phương thức revokeAccessToken
trên Laravel\Passport\TokenRepository
. Bạn có thể thu hồi các refresh của một token phương thức revokeRefreshTokensByAccessTokenId
trên Laravel\Passport\RefreshTokenRepository
. Các class này có thể được resolve bằng cách sử dụng service container:
use Laravel\Passport\TokenRepository;
use Laravel\Passport\RefreshTokenRepository;
$tokenRepository = app(TokenRepository::class);
$refreshTokenRepository = app(RefreshTokenRepository::class);
// Revoke an access token...
$tokenRepository->revokeAccessToken($tokenId);
// Revoke all of the token's refresh tokens...
$refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId);
Khi token bị thu hồi hoặc bị hết hạn, bạn có thể muốn xóa chúng ra khỏi cơ sở dữ liệu. Passport có kèm theo một lệnh Artisan passport:purge
có thể thực hiện việc này cho bạn:
# Purge revoked and expired tokens and auth codes...
php artisan passport:purge
# Only purge revoked tokens and auth codes...
php artisan passport:purge --revoked
# Only purge expired tokens and auth codes...
php artisan passport:purge --expired
Bạn cũng có thể cấu hình một scheduled job trong class App\Console\Kernel
của application của bạn để tự động lọc token của bạn theo một schedule:
/**
* Define the application's command schedule.
*
* @param \Illuminate\Console\Scheduling\Schedule $schedule
* @return void
*/
protected function schedule(Schedule $schedule)
{
$schedule->command('passport:purge')->hourly();
}
Việc Authorization Code grant với "Proof Key for Code Exchange" (PKCE) là một cách an toàn để xác thực các trang web hoặc các ứng dụng truy cập vào API của bạn. Grant này sẽ được sử dụng khi bạn không thể đảm bảo rằng client secret sẽ được lưu trữ một cách an toàn hoặc cũng có thể là để giảm thiểu nguy cơ bị kẻ tấn công chặn authorization code. Sự kết hợp giữa một "code verifier" và một "code challenge" sẽ thay thế client secret khi trao đổi authorization code để lấy một access token.
Trước khi ứng dụng của bạn có thể phát hành token thông qua authorization code grant với PKCE, bạn sẽ cần tạo một ứng dụng client hỗ trợ PKCE. Bạn có thể thực hiện việc này bằng lệnh Artisan passport:client
với tùy chọn --public
:
php artisan passport:client --public
Vì authorization grant này không cung cấp một client secret, nên các nhà phát triển sẽ cần phải tạo ra một code verifier và một code challenge để yêu cầu token.
Code verifier phải là một chuỗi ngẫu nhiên từ 43 đến 128 ký tự chứa các chữ cái, số, và các ký tự "-"
, "."
, "_"
, "~"
, như được định nghĩa trong tài liệu RFC 7636 đặc điểm kỹ thuật.
Code challenge phải là một chuỗi được mã hóa Base64 với URL và các ký tự an toàn cho tên file. Các ký tự ở cuối dấu '='
phải được loại bỏ và không được có dấu ngắt dòng, khoảng trắng hoặc các ký tự bổ sung khác.
$encoded = base64_encode(hash('sha256', $code_verifier, true));
$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');
Sau khi một ứng dụng client đã được tạo xong, bạn có thể sử dụng ID của ứng dụng client đó và code verifier và code challenge đã tạo để yêu cầu một authorization code và một access token từ ứng dụng của bạn. Đầu tiên, ứng dụng đang sử dụng phải thực hiện một request chuyển hướng đến route /oauth/authorize
của ứng dụng của bạn:
use Illuminate\Http\Request;
use Illuminate\Support\Str;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$request->session()->put(
'code_verifier', $code_verifier = Str::random(128)
);
$codeChallenge = strtr(rtrim(
base64_encode(hash('sha256', $code_verifier, true))
, '='), '+/', '-_');
$query = http_build_query([
'client_id' => 'client-id',
'redirect_uri' => 'http://third-party-app.com/callback',
'response_type' => 'code',
'scope' => '',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
]);
return redirect('http://passport-app.test/oauth/authorize?'.$query);
});
Nếu người dùng chấp thuận yêu cầu authorization, họ sẽ được chuyển hướng trở lại ứng dụng mà họ đang sử dụng. Người dùng api của bạn nên xác thực thông số state
so với giá trị đã được lưu trữ trước khi được chuyển hướng, như trong Authorization Code Grant tiêu chuẩn.
Nếu thông số state khớp, Người dùng api của bạn nên đưa ra một request POST
cho ứng dụng của bạn để yêu cầu một access token. Yêu cầu này phải chứa authorization code do ứng dụng của bạn cấp khi người dùng chấp thuận yêu cầu authorization cùng với code verifier đã được tạo ra ban đầu:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
Route::get('/callback', function (Request $request) {
$state = $request->session()->pull('state');
$codeVerifier = $request->session()->pull('code_verifier');
throw_unless(
strlen($state) > 0 && $state === $request->state,
InvalidArgumentException::class
);
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'client-id',
'redirect_uri' => 'http://third-party-app.com/callback',
'code_verifier' => $codeVerifier,
'code' => $request->code,
]);
return $response->json();
});
{note} Chúng tôi khuyên bạn không nên sử dụng password grant token nữa. Thay vào đó, bạn nên chọn loại grant mà được OAuth2 Server đề xuất.
OAuth2 password grant cho phép các client bên thứ nhất, chẳng hạn như một application mobile trong tổ chức của bạn, có được access token bằng địa chỉ email hoặc tên người dùng và mật khẩu của họ. Điều này cho phép bạn phát hành access token một cách an toàn cho client bên thứ nhất mà không yêu cầu người dùng của bạn thực hiện toàn bộ các luồng chuyển hướng OAuth2 authorization code.
Trước khi application của bạn có thể phát hành token thông qua password grant, bạn sẽ cần phải tạo một password grant client. Bạn có thể làm điều này bằng cách sử dụng lệnh Artisan passport:client
với tùy chọn --password
. Nếu bạn đã chạy lệnh passport:install
, thì bạn không cần phải chạy lệnh này:
php artisan passport:client --password
Khi bạn đã tạo một password grant client, bạn có thể yêu cầu access token bằng cách đưa ra một request POST
cho route /oauth/token
với địa chỉ email và mật khẩu của người dùng. Hãy nhớ rằng, route này đã được đăng ký bằng phương thức Passport::routes
nên bạn không cần phải định nghĩa lại chúng. Nếu yêu cầu thành công, bạn sẽ nhận được một access_token
và một refresh_token
trong JSON response từ server:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => '[email protected]',
'password' => 'my-password',
'scope' => '',
]);
return $response->json();
{tip} Hãy nhớ rằng, access token sẽ mặc định là tồn tại mãi mãi. Tuy nhiên, bạn có thể thoải mái cấu hình maximum vòng đời access token của bạn nếu cần.
Khi sử dụng password grant hoặc chứng chỉ client grant, bạn có thể muốn ủy quyền token cho tất cả các scope được application của bạn hỗ trợ. Bạn có thể làm điều này bằng cách yêu cầu scope *
. Nếu bạn yêu cầu scope là *
, thì phương thức can
trên instance token sẽ luôn trả về true
. Scope này chỉ có thể được gán cho những token mà được cấp bằng password
hoặc client_credentials
grant`:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => '[email protected]',
'password' => 'my-password',
'scope' => '*',
]);
Nếu ứng dụng của bạn sử dụng nhiều hơn một user provider để xác thực, bạn có thể chỉ định user provider nào sẽ được password grant client sử dụng bằng cách cung cấp thêm một tùy chọn --provider
khi tạo client thông qua lệnh artisan passport:client --password
. Tên user provider phải khớp với tên một user provider đã được định nghĩa trong file cấu hình config/auth.php
của application của bạn. Sau đó, bạn có thể bảo vệ route của bạn thông qua middleware để đảm bảo rằng chỉ những người dùng từ user provider được chỉ định mới được cấp quyền.
Khi xác thực bằng password grant, Passport sẽ sử dụng thuộc tính email
của model authenticatable của bạn làm "username". Tuy nhiên, bạn có thể tùy chỉnh hành động này bằng cách định nghĩa phương thức findForPassport
trên model của bạn:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
/**
* Find the user instance for the given username.
*
* @param string $username
* @return \App\Models\User
*/
public function findForPassport($username)
{
return $this->where('username', $username)->first();
}
}
Khi xác thực bằng password grant, Passport sẽ sử dụng thuộc tính password
trong model của bạn để xác thực mật khẩu đã cho. Nếu model của bạn không có thuộc tính password
hoặc bạn muốn tùy chỉnh logic xác thực password, bạn có thể định nghĩa phương thức validateForPassportPasswordGrant
trong model của bạn:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
/**
* Validate the password of the user for the Passport password grant.
*
* @param string $password
* @return bool
*/
public function validateForPassportPasswordGrant($password)
{
return Hash::check($password, $this->password);
}
}
{note} Chúng tôi khuyên bạn không nên sử dụng implicit grant token nữa. Thay vào đó, bạn nên chọn loại grant mà được OAuth2 Server đề xuất.
Grant ẩn tương tự như authorization code grant; tuy nhiên, token được trả về cho client mà không cần thông qua authorization code. Grant này được sử dụng phổ biến nhất cho các application JavaScript hoặc mobile application nơi mà thông tin đăng nhập của client không thể được lưu trữ an toàn. Để kích hoạt grant, hãy gọi phương thức enableImplicitGrant
trong the boot
method of your application's App\Providers\AuthServiceProvider
class: phương thức boot
của lớp App\Providers\AuthServiceProvider
trong ứng dụng của bạn:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::enableImplicitGrant();
}
Khi grant này đã được bật, nhà phát triển có thể sử dụng client ID của chính họ để yêu cầu access token từ application của bạn. Ứng dụng của nhà phát triển sẽ tạo một yêu cầu chuyển hướng đến route /oauth/authorize
của application của bạn như sau:
use Illuminate\Http\Request;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$query = http_build_query([
'client_id' => 'client-id',
'redirect_uri' => 'http://third-party-app.com/callback',
'response_type' => 'token',
'scope' => '',
'state' => $state,
]);
return redirect('http://passport-app.test/oauth/authorize?'.$query);
});
{tip} Hãy nhớ rằng, route
/oauth/authorize
đã được định nghĩa bởi phương thứcPassport::routes
. Bạn không cần phải định nghĩa route này.
Chứng chỉ client grant thích hợp cho việc authentication machine-to-machine. Ví dụ: bạn có thể sử dụng grant này trong một scheduled job đang thực hiện công việc bảo trì qua API.
Trước khi ứng dụng của bạn có thể phát hành mã token thông qua chứng chỉ client grant, bạn sẽ cần tạo một client chứng chỉ client grant. Bạn có thể thực hiện việc này bằng cách sử dụng tùy chọn --client
trong lệnh Artisan passport:client
:
php artisan passport:client --client
Tiếp theo, để sử dụng loại grant này, bạn cần thêm middleware CheckClientCredentials
vào thuộc tính $routeMiddleware
trong file app/Http/Kernel.php
của bạn:
use Laravel\Passport\Http\Middleware\CheckClientCredentials;
protected $routeMiddleware = [
'client' => CheckClientCredentials::class,
];
Sau đó gắn middleware này vào một route:
Route::get('/orders', function(Request $request) {
...
})->middleware('client');
Để hạn chế quyền truy cập vào route đối với một số scope cụ thể, bạn có thể cung cấp một danh sách các scope yêu cầu bắt buộc khi gắn middleware client
vào route, bạn có thể được phân chia các scope này bằng dấu phẩy:
Route::get('/orders', function (Request $request) {
...
})->middleware('client:check-status,your-scope');
Để lấy một token của một loại grant này, hãy tạo một request đến oauth/token
endpoint:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('http://passport-app.test/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'scope' => 'your-scope',
]);
return $response->json()['access_token'];
Đôi khi, người dùng của bạn có thể muốn phát hành access token cho chính họ mà không cần thông qua luồng chuyển hướng authorization code thông thường. Việc cho phép người dùng phát hành token cho chính họ thông qua giao diện người dùng của application của bạn có thể hữu ích khi cho phép người dùng thử nghiệm API của bạn hoặc có thể dùng như một cách tiếp cận đơn giản hơn khi phát hành access token nói chung.
{tip} Nếu ứng dụng của bạn sử dụng Passport chủ yếu là để cấp các mã personal access token, thì hãy cân nhắc sử dụng Laravel Sanctum, đây là thư viện gọn nhẹ của Laravel để cấp mã API access token.
Trước khi application của bạn có thể phát hành một personal access token, bạn sẽ cần tạo một personal access client. Bạn có thể làm điều này bằng cách chạy lệnh Artisan passport:client
với tùy chọn --personal
. Nếu bạn đã chạy lệnh passport:install
, bạn không cần chạy lệnh này:
php artisan passport:client --personal
Sau khi tạo personal access client của bạn, hãy set một giá ID của client và một giá trị secret vào trong file .env
của ứng dụng của bạn:
PASSPORT_PERSONAL_ACCESS_CLIENT_ID="client-id-value"
PASSPORT_PERSONAL_ACCESS_CLIENT_SECRET="unhashed-client-secret-value"
Khi bạn đã tạo một personal access client, bạn có thể phát hành token cho một người dùng bằng cách sử dụng phương thức createToken
trên instance model User
đó. Phương thức createToken
chấp nhận tên của token làm tham số đầu tiên và một mảng tùy chọn scopes làm tham số thứ hai:
use App\Models\User;
$user = User::find(1);
// Creating a token without scopes...
$token = $user->createToken('Token Name')->accessToken;
// Creating a token with scopes...
$token = $user->createToken('My Token', ['place-orders'])->accessToken;
Passport cũng chứa một JSON API để quản lý personal access token. Bạn có thể kết hợp api này với frontend của riêng bạn để cung cấp cho người dùng bảng điều khiển để quản lý personal access token của họ. Dưới đây, chúng ta sẽ xem qua tất cả các API endpoint để quản lý personal access token. Để thuận tiện, chúng ta sẽ sử dụng Axios để thực hiện các HTTP request.
JSON API được bảo vệ bởi middleware web
và auth
; do đó, nó chỉ có thể được gọi từ ứng dụng của bạn. Nó không thể được gọi từ một nguồn ở bên ngoài nào khác.
GET /oauth/scopes
Route này trả về tất cả scopes được định nghĩa cho application của bạn. Bạn có thể sử dụng route này để liệt kê scope mà người dùng có thể gán cho một personal access token:
axios.get('/oauth/scopes')
.then(response => {
console.log(response.data);
});
GET /oauth/personal-access-tokens
Route này trả về tất cả các personal access token mà người dùng hiện tại đã tạo. Điều này sẽ hữu ích khi liệt kê tất cả các token của người dùng để họ có thể chỉnh sửa hoặc huỷ bỏ chúng:
axios.get('/oauth/personal-access-tokens')
.then(response => {
console.log(response.data);
});
POST /oauth/personal-access-tokens
Route này sẽ tạo personal access token mới. Nó đòi hỏi hai phần dữ liệu: một là name
của token và một là scopes
cần được gán cho token:
const data = {
name: 'Token Name',
scopes: []
};
axios.post('/oauth/personal-access-tokens', data)
.then(response => {
console.log(response.data.accessToken);
})
.catch (response => {
// List errors on response...
});
DELETE /oauth/personal-access-tokens/{token-id}
Route này có thể được sử dụng để huỷ bỏ personal access token:
axios.delete('/oauth/personal-access-tokens/' + tokenId);
Passport có chứa một authentication guard sẽ kiểm tra access token khi có request đến. Sau khi bạn đã cấu hình xong guard api
sử dụng passport
driver, bạn chỉ cần cài đặt middleware auth:api
vào bất kỳ route nào mà yêu cầu một access token hợp lệ:
Route::get('/user', function () {
//
})->middleware('auth:api');
{note} Nếu bạn đang sử dụng client credentials grant, bạn nên sử dụng middleware
client
để bảo vệ các route của bạn thay vì middlewareauth:api
.
Nếu ứng dụng của bạn xác thực các loại người dùng khác nhau mà dùng các model Eloquent khác nhau, bạn có thể sẽ cần định nghĩa một cấu hình guard cho từng loại user providers trong ứng dụng của bạn. Điều này cho phép bạn bảo vệ các request dành cho các user providers cụ thể. Ví dụ: cho cấu hình guard của file cấu hình config/auth.php
sau:
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'api-customers' => [
'driver' => 'passport',
'provider' => 'customers',
],
Route sau sẽ sử dụng guard api-customers
, sử dụng user provider customers
, để xác thực các request đến:
Route::get('/customer', function () {
//
})->middleware('auth:api-customers');
{tip} Để biết thêm thông tin về cách sử dụng nhiều user provider cùng với Passport, vui lòng tham khảo thêm tài liệu về password grant.
Khi gọi các route mà được bảo vệ bởi Passport, thì API bên thứ ba của application của bạn nên cài đặt access token của họ dưới dạng một Bearer
token trong header Authorization
trong request của họ. Ví dụ: khi sử dụng thư viện Guzzle HTTP:
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'Accept' => 'application/json',
'Authorization' => 'Bearer '.$accessToken,
])->get('https://passport-app.test/api/user');
return $response->json();
Scope cho phép API client của bạn yêu cầu một nhóm quyền cụ thể khi request authorization để truy cập vào tài khoản. Ví dụ: nếu bạn đang xây dựng một application thương mại điện tử, không phải tất cả API bên thứ ba nào cũng sẽ cần khả năng đặt hàng. Thay vào đó, bạn có thể cho phép bên thứ ba chỉ request authorization truy cập vào được trạng thái giao hàng. Nói cách khác, scope cho phép người dùng application của bạn giới hạn các hành động mà application của bên thứ ba có thể thực hiện.
Bạn có thể định nghĩa scope của API bằng phương thức Passport::tokensCan
trong phương thức boot
của class App\Providers\AuthServiceProvider
của application. Phương thức tokensCan
chấp nhận một loạt các tên scope và mô tả của nó. Mô tả scope có thể là bất cứ điều gì bạn muốn và sẽ được hiển thị cho người dùng trên màn hình phê duyệt authorization:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensCan([
'place-orders' => 'Place orders',
'check-status' => 'Check order status',
]);
}
Nếu một client không yêu cầu bất kỳ scope nào, bạn có thể cấu hình Passport server của bạn để gắn một scope(s) mặc định vào mã token bằng phương thức setDefaultScope
. Thông thường, bạn nên gọi phương thức này từ phương thức boot
trong class App\Providers\AuthServiceProvider
của application:
use Laravel\Passport\Passport;
Passport::tokensCan([
'place-orders' => 'Place orders',
'check-status' => 'Check order status',
]);
Passport::setDefaultScope([
'check-status',
'place-orders',
]);
Khi yêu cầu access token bằng cách sử dụng authorization code grant, thì bên thứ ba nên chỉ định scope mà họ mong muốn bằng tham số chuỗi truy vấn scope
. Tham số scope
phải là một danh sách scope đã được phân tách bằng dấu cách:
Route::get('/redirect', function () {
$query = http_build_query([
'client_id' => 'client-id',
'redirect_uri' => 'http://example.com/callback',
'response_type' => 'code',
'scope' => 'place-orders check-status',
]);
return redirect('http://passport-app.test/oauth/authorize?'.$query);
});
Nếu bạn đang phát hành personal access token bằng cách sử dụng phương thức createToken
của model App\Models\User
, bạn có thể truyền một mảng scope mong muốn làm tham số thứ hai cho phương thức:
$token = $user->createToken('My Token', ['place-orders'])->accessToken;
Passport có chứa hai middleware có thể được sử dụng để xác minh xem request đến đã được authenticate với một token mà đã được cấp với một scope hay chưa. Để bắt đầu, hãy thêm middleware sau vào thuộc tính $routeMiddleware
trong file app/Http/Kernel.php
của bạn:
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
Middleware scopes
có thể được chỉ định cho một route để xác minh xem access token của một request đến application đã có tất cả những scope đã được liệt kê hay chưa:
Route::get('/orders', function () {
// Access token has both "check-status" and "place-orders" scopes...
})->middleware(['auth:api', 'scopes:check-status,place-orders']);
Middleware scope
có thể được chỉ định cho một route để xác minh xem access token của request đến đã có ít nhất một trong những scope đã được liệt kê hay chưa:
Route::get('/orders', function () {
// Access token has either "check-status" or "place-orders" scope...
})->middleware(['auth:api', 'scope:check-status,place-orders']);
Khi một request được authenticate bằng access token đã vào đến application của bạn, bạn vẫn có thể kiểm tra xem token đã có scope hay chưa bằng cách sử dụng phương thức tokenCan
trên instance App\Models\User
đã được xác thực:
use Illuminate\Http\Request;
Route::get('/orders', function (Request $request) {
if ($request->user()->tokenCan('place-orders')) {
//
}
});
Phương thức scopeIds
sẽ trả về một mảng gồm tất cả các ID và tên đã được định nghĩa:
use Laravel\Passport\Passport;
Passport::scopeIds();
Phương thức scopes
sẽ trả về một mảng gồm tất cả các scope đã được định nghĩa dưới dạng các instance của Laravel\Passport\Scope
:
Passport::scopes();
Phương thức scopesFor
sẽ trả về một mảng các instance Laravel\Passport\Scope
mà khớp với các ID và tên đã cho:
Passport::scopesFor(['place-orders', 'check-status']);
Bạn có thể kiểm tra xem một scope nhất định đã được định nghĩa hay chưa bằng cách sử dụng phương thức hasScope
:
Passport::hasScope('place-orders');
Khi xây dựng một API, nó có thể rất hữu ích khi sử dụng API của riêng bạn từ application JavaScript. Cách tiếp cận này cho phép application của bạn sử dụng cùng API mà bạn đang chia sẻ với mọi người. API tương tự cũng có thể được sử dụng bởi application web, application di động, application của bên thứ ba hoặc bất kỳ SDK nào bạn có thể publish trên các trình quản lý package khác nhau.
Thông thường, nếu bạn muốn sử dụng API từ application JavaScript của bạn, bạn cần phải tự gửi access token đến application và truyền nó theo mỗi request đến application của bạn. Tuy nhiên, Passport có chứa một middleware có thể xử lý việc này cho bạn. Tất cả những gì bạn cần làm là thêm một middleware CreateFreshApiToken
vào middleware group web
trong file app/Http/Kernel.php
của bạn:
'web' => [
// Other middleware...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
{note} Bạn nên đảm bảo rằng middleware
CreateFreshApiToken
sẽ được khai báo cuối cùng trong stack middleware của bạn.
Passport middleware này sẽ gán một cookie laravel_token
vào các response gửi về cho bạn. Cookie này chứa JWT đã được mã hóa mà Passport sẽ sử dụng để xác thực các API request từ application JavaScript của bạn. JWT có thời gian tồn tại bằng với giá trị cấu hình session.lifetime
của bạn. Bây giờ, vì trình duyệt sẽ tự động gửi cookie này cho tất cả các request tiếp theo, nên bạn có thể thực hiện các request đối với API của application mà không cần phải truyền một access token:
axios.get('/api/user')
.then(response => {
console.log(response.data);
});
Nếu cần, bạn có thể tùy biến tên cookie laravel_token
bằng phương thức Passport::cookie
. Thông thường, phương thức này sẽ được gọi từ phương thức boot
trong class App\Providers\AuthServiceProvider
của application của bạn:
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::cookie('custom_name');
}
Khi sử dụng phương thức xác thực này, bạn sẽ cần đảm bảo một header CSRF token hợp lệ đã được chứa trong các request của bạn. Mặc định, Laravel JavaScript scaffolding đã chứa một instance Axios, instance này sẽ tự động sử dụng giá trị cookie XSRF-TOKEN
được mã hóa để gửi một header X-XSRF-TOKEN
cho các request có cùng origin.
{tip} Nếu bạn chọn gửi header
X-CSRF-TOKEN
thay vìX-XSRF-TOKEN
, bạn sẽ cần sử dụng một token chưa được mã hóa docsrf_token()
cung cấp.
Passport sẽ tạo ra các event mỗi khi phát hành một access token và một refresh token. Bạn có thể sử dụng các event này để bỏ bớt hoặc thu hồi các access token khác trong cơ sở dữ liệu của bạn. Nếu bạn muốn, bạn có thể gán một listener vào các event này trong class App\Providers\EventServiceProvider
của application của bạn:
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
'Laravel\Passport\Events\AccessTokenCreated' => [
'App\Listeners\RevokeOldTokens',
],
'Laravel\Passport\Events\RefreshTokenCreated' => [
'App\Listeners\PruneOldTokens',
],
];
Phương thức actingAs
của Passport có thể được sử dụng để chỉ định một người dùng với scope của họ. Tham số đầu tiên được đưa vào cho phương thức actingAs
là instance user và tham số thứ hai là một mảng scope được cấp cho token đó của người dùng:
use App\Models\User;
use Laravel\Passport\Passport;
public function test_servers_can_be_created()
{
Passport::actingAs(
User::factory()->create(),
['create-servers']
);
$response = $this->post('/api/create-server');
$response->assertStatus(201);
}
Phương thức actingAsClient
của Passport có thể được sử dụng để chỉ định những client hiện đang được xác thực cũng như scope của nó. Tham số đầu tiên được cung cấp cho phương thức actingAsClient
là instance client và tham số thứ hai là một mảng scope sẽ được cấp cho token của client đó:
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
public function test_orders_can_be_retrieved()
{
Passport::actingAsClient(
Client::factory()->create(),
['check-status']
);
$response = $this->get('/api/orders');
$response->assertStatus(200);
}
entry