Passport
这是一篇协同翻译的文章,你可以点击『我来翻译』按钮来参与翻译。
Laravel Passport
简介
Laravel Passport 能在几分钟内为您的 Laravel 应用提供完整的 OAuth2 服务器实现。Passport 基于 League OAuth2 server 构建,该项目由 Andy Millington 和 Simon Hamp 维护。
[!注意]
本文档假设您已熟悉 OAuth2。如果您对 OAuth2 不了解,建议先熟悉其通用的 术语 和特性再继续阅读。
Passport 还是 Sanctum?
在开始之前,您可能需要确定您的应用更适合使用 Laravel Passport 还是 Laravel Sanctum。如果您的应用必须支持 OAuth2,那么应该使用 Laravel Passport。
但是,如果您需要为单页应用、移动应用提供认证,或仅需发放 API 令牌,那么应该使用 Laravel Sanctum。Laravel Sanctum 不支持 OAuth2,但它提供了更简单的 API 认证开发体验。
安装
您可以通过 install:api
Artisan 命令安装 Laravel Passport:
php artisan install:api --passport
该命令将发布并运行必要的数据库迁移,创建用于存储 OAuth2 客户端和访问令牌的表。同时还会创建生成安全访问令牌所需的加密密钥。
运行 install:api
命令后,将 Laravel\Passport\HasApiTokens
trait 和 Laravel\Passport\Contracts\OAuthenticatable
接口添加到您的 App\Models\User
模型中。该 trait 会为模型提供一些辅助方法,用于检查已认证用户的令牌和作用域:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
最后,在应用的 config/auth.php
配置文件中,应定义一个 api
认证守卫并将 driver
选项设置为 passport
。这将指示应用在认证传入的 API 请求时使用 Passport 的 TokenGuard
:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
部署 Passport
首次将 Passport 部署到应用服务器时,可能需要运行 passport:keys
命令。该命令生成 Passport 生成访问令牌所需的加密密钥。生成的密钥通常不纳入版本控制:
php artisan passport:keys
如有需要,可以定义 Passport 密钥的加载路径。使用 Passport::loadKeysFrom
方法实现此功能。通常,应在应用的 App\Providers\AppServiceProvider
类的 boot
方法中调用此方法:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::loadKeysFrom(__DIR__.'/../secrets/oauth');
}
从环境变量加载密钥
或者,您可以使用 vendor:publish
Artisan 命令发布 Passport 的配置文件:
php artisan vendor:publish --tag=passport-config
发布配置文件后,可以通过环境变量定义应用的加密密钥:
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-----"
升级 Passport
升级到 Passport 的新主要版本时,请务必仔细查阅 升级指南。
配置
令牌生命周期
默认情况下,Passport 颁发长期有效的访问令牌,有效期为一年。如需配置更长或更短的令牌生命周期,可以使用 tokensExpireIn
, refreshTokensExpireIn
, 和 personalAccessTokensExpireIn
方法。这些方法应在应用的 App\Providers\AppServiceProvider
类的 boot
方法中调用:
use Carbon\CarbonInterval;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::tokensExpireIn(CarbonInterval::days(15));
Passport::refreshTokensExpireIn(CarbonInterval::days(30));
Passport::personalAccessTokensExpireIn(CarbonInterval::months(6));
}
[!警告]
Passport 数据库表中的expires_at
列是只读的,仅用于显示目的。颁发令牌时,Passport 将过期信息存储在签名和加密的令牌中。如需使令牌失效,应 撤销令牌.
Overriding Default Models
You are free to extend the models used internally by Passport by defining your own model and extending the corresponding Passport model:
use Laravel\Passport\Client as PassportClient;
class Client extends PassportClient
{
// ...
}
After defining your model, you may instruct Passport to use your custom model via the Laravel\Passport\Passport
class. Typically, you should inform Passport about your custom models in the boot
method of your application's App\Providers\AppServiceProvider
class:
use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\DeviceCode;
use App\Models\Passport\RefreshToken;
use App\Models\Passport\Token;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::useTokenModel(Token::class);
Passport::useRefreshTokenModel(RefreshToken::class);
Passport::useAuthCodeModel(AuthCode::class);
Passport::useClientModel(Client::class);
Passport::useDeviceCodeModel(DeviceCode::class)
}
Overriding Routes
Sometimes you may wish to customize the routes defined by Passport. To achieve this, you first need to ignore the routes registered by Passport by adding Passport::ignoreRoutes
to the register
method of your application's AppServiceProvider
:
use Laravel\Passport\Passport;
/**
* Register any application services.
*/
public function register(): void
{
Passport::ignoreRoutes();
}
Then, you may copy the routes defined by Passport in its routes file to your application's routes/web.php
file and modify them to your liking:
Route::group([
'as' => 'passport.',
'prefix' => config('passport.path', 'oauth'),
'namespace' => '\Laravel\Passport\Http\Controllers',
], function () {
// Passport routes...
});
Authorization Code Grant
Using OAuth2 via authorization codes is how most developers are familiar with OAuth2. When using authorization codes, a client application will redirect a user to your server where they will either approve or deny the request to issue an access token to the client.
To get started, we need to instruct Passport how to return our "authorization" view.
All the authorization view's rendering logic may be customized using the appropriate methods available via the Laravel\Passport\Passport
class. Typically, you should call this method from the boot
method of your application's App\Providers\AppServiceProvider
class:
use Inertia\Inertia;
use Laravel\Passport\Passport;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// By providing a view name...
Passport::authorizationView('auth.oauth.authorize');
// By providing a closure...
Passport::authorizationView(
fn ($parameters) => Inertia::render('Auth/OAuth/Authorize', [
'request' => $parameters['request'],
'authToken' => $parameters['authToken'],
'client' => $parameters['client'],
'user' => $parameters['user'],
'scopes' => $parameters['scopes'],
])
);
}
Passport will automatically define the /oauth/authorize
route that returns this view. Your auth.oauth.authorize
template should include a form that makes a POST request to the passport.authorizations.approve
route to approve the authorization and a form that makes a DELETE request to the passport.authorizations.deny
route to deny the authorization. The passport.authorizations.approve
and passport.authorizations.deny
routes expect state
, client_id
, and auth_token
fields.
Managing Clients
Developers building applications that need to interact with your application's API will need to register their application with yours by creating a "client". Typically, this consists of providing the name of their application and a URI that your application can redirect to after users approve their request for authorization.
First-Party Clients
The simplest way to create a client is using the passport:client
Artisan command. This command may be used to create first-party clients or testing your OAuth2 functionality. When you run the passport:client
command, Passport will prompt you for more information about your client and will provide you with a client ID and secret:
php artisan passport:client
If you would like to allow multiple redirect URIs for your client, you may specify them using a comma-delimited list when prompted for the URI by the passport:client
command. Any URIs which contain commas should be URI encoded:
https://third-party-app.com/callback,https://example.com/oauth/redirect
Third-Party Clients
Since your application's users will not be able to utilize the passport:client
command, you may use createAuthorizationCodeGrantClient
method of the Laravel\Passport\ClientRepository
class to register a client for a given user:
use App\Models\User;
use Laravel\Passport\ClientRepository;
$user = User::find($userId);
// Creating an OAuth app client that belongs to the given user...
$client = app(ClientRepository::class)->createAuthorizationCodeGrantClient(
user: $user,
name: 'Example App',
redirectUris: ['https://third-party-app.com/callback'],
confidential: false,
enableDeviceFlow: true
);
// Retrieving all the OAuth app clients that belong to the user...
$clients = $user->oauthApps()->get();
The createAuthorizationCodeGrantClient
method returns an instance of Laravel\Passport\Client
. You may display the $client->id
as the client ID and $client->plainSecret
as the client secret to the user.
Requesting Tokens
Redirecting for Authorization
Once a client has been created, developers may use their client ID and secret to request an authorization code and access token from your application. First, the consuming application should make a redirect request to your application's /oauth/authorize
route like so:
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' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
'state' => $state,
// 'prompt' => '', // "none", "consent", or "login"
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
The prompt
parameter may be used to specify the authentication behavior of the Passport application.
If the prompt
value is none
, Passport will always throw an authentication error if the user is not already authenticated with the Passport application. If the value is consent
, Passport will always display the authorization approval screen, even if all scopes were previously granted to the consuming application. When the value is login
, the Passport application will always prompt the user to re-login to the application, even if they already have an existing session.
If no prompt
value is provided, the user will be prompted for authorization only if they have not previously authorized access to the consuming application for the requested scopes.
[!NOTE]
Remember, the/oauth/authorize
route is already defined by Passport. You do not need to manually define this route.
Approving the Request
When receiving authorization requests, Passport will automatically respond based on the value of prompt
parameter (if present) and may display a template to the user allowing them to approve or deny the authorization request. If they approve the request, they will be redirected back to the redirect_uri
that was specified by the consuming application. The redirect_uri
must match the redirect
URL that was specified when the client was created.
Sometimes you may wish to skip the authorization prompt, such as when authorizing a first-party client. You may accomplish this by extending the Client
model and defining a skipsAuthorization
method. If skipsAuthorization
returns true
the client will be approved and the user will be redirected back to the redirect_uri
immediately, unless the consuming application has explicitly set the prompt
parameter when redirecting for authorization:
<?php
namespace App\Models\Passport;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Passport\Client as BaseClient;
class Client extends BaseClient
{
/**
* Determine if the client should skip the authorization prompt.
*
* @param \Laravel\Passport\Scope[] $scopes
*/
public function skipsAuthorization(Authenticatable $user, array $scopes): bool
{
return $this->firstParty();
}
}
Converting Authorization Codes to Access Tokens
If the user approves the authorization request, they will be redirected back to the consuming application. The consumer should first verify the state
parameter against the value that was stored prior to the redirect. If the state parameter matches then the consumer should issue a POST
request to your application to request an access token. The request should include the authorization code that was issued by your application when the user approved the authorization request:
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,
'Invalid state value.'
);
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'redirect_uri' => 'https://third-party-app.com/callback',
'code' => $request->code,
]);
return $response->json();
});
This /oauth/token
route will return a JSON response containing access_token
, refresh_token
, and expires_in
attributes. The expires_in
attribute contains the number of seconds until the access token expires.
[!NOTE]
Like the/oauth/authorize
route, the/oauth/token
route is defined for you by Passport. There is no need to manually define this route.
Managing Tokens
You may retrieve user's authorized tokens using the tokens
method of the Laravel\Passport\HasApiTokens
trait. For example, this may be used to offer your users a dashboard to keep track of their connections with third-party applications:
use App\Models\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;
$user = User::find($userId);
// Retrieving all of the valid tokens for the user...
$tokens = $user->tokens()
->where('revoked', false)
->where('expires_at', '>', Date::now())
->get();
// Retrieving all the user's connections to third-party OAuth app clients...
$connections = $tokens->load('client')
->reject(fn (Token $token) => $token->client->firstParty())
->groupBy('client_id')
->map(fn (Collection $tokens) => [
'client' => $tokens->first()->client,
'scopes' => $tokens->pluck('scopes')->flatten()->unique()->values()->all(),
'tokens_count' => $tokens->count(),
])
->values();
Refreshing Tokens
If your application issues short-lived access tokens, users will need to refresh their access tokens via the refresh token that was provided to them when the access token was issued:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'refresh_token',
'refresh_token' => 'the-refresh-token',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // Required for confidential clients only...
'scope' => 'user:read orders:create',
]);
return $response->json();
This /oauth/token
route will return a JSON response containing access_token
, refresh_token
, and expires_in
attributes. The expires_in
attribute contains the number of seconds until the access token expires.
Revoking Tokens
You may revoke a token by using the revoke
method on the Laravel\Passport\Token
model. You may revoke a token's refresh token using the revoke
method on the Laravel\Passport\RefreshToken
model:
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
$token = Passport::token()->find($tokenId);
// Revoke an access token...
$token->revoke();
// Revoke the token's refresh token...
$token->refreshToken?->revoke();
// Revoke all of the user's tokens...
User::find($userId)->tokens()->each(function (Token $token) {
$token->revoke();
$token->refreshToken?->revoke();
});
Purging Tokens
When tokens have been revoked or expired, you might want to purge them from the database. Passport's included passport:purge
Artisan command can do this for you:
# Purge revoked and expired tokens, auth codes, and device codes...
php artisan passport:purge
# Only purge tokens expired for more than 6 hours...
php artisan passport:purge --hours=6
# Only purge revoked tokens, auth codes, and device codes...
php artisan passport:purge --revoked
# Only purge expired tokens, auth codes, and device codes...
php artisan passport:purge --expired
You may also configure a scheduled job in your application's routes/console.php
file to automatically prune your tokens on a schedule:
use Illuminate\Support\Facades\Schedule;
Schedule::command('passport:purge')->hourly();
Authorization Code Grant With PKCE
The Authorization Code grant with "Proof Key for Code Exchange" (PKCE) is a secure way to authenticate single page applications or mobile applications to access your API. This grant should be used when you can't guarantee that the client secret will be stored confidentially or in order to mitigate the threat of having the authorization code intercepted by an attacker. A combination of a "code verifier" and a "code challenge" replaces the client secret when exchanging the authorization code for an access token.
Creating the Client
Before your application can issue tokens via the authorization code grant with PKCE, you will need to create a PKCE-enabled client. You may do this using the passport:client
Artisan command with the --public
option:
php artisan passport:client --public
Requesting Tokens
Code Verifier and Code Challenge
As this authorization grant does not provide a client secret, developers will need to generate a combination of a code verifier and a code challenge in order to request a token.
The code verifier should be a random string of between 43 and 128 characters containing letters, numbers, and "-"
, "."
, "_"
, "~"
characters, as defined in the RFC 7636 specification.
The code challenge should be a Base64 encoded string with URL and filename-safe characters. The trailing '='
characters should be removed and no line breaks, whitespace, or other additional characters should be present.
$encoded = base64_encode(hash('sha256', $codeVerifier, true));
$codeChallenge = strtr(rtrim($encoded, '='), '+/', '-_');
Redirecting for Authorization
Once a client has been created, you may use the client ID and the generated code verifier and code challenge to request an authorization code and access token from your application. First, the consuming application should make a redirect request to your application's /oauth/authorize
route:
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', $codeVerifier = Str::random(128)
);
$codeChallenge = strtr(rtrim(
base64_encode(hash('sha256', $codeVerifier, true))
, '='), '+/', '-_');
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
'state' => $state,
'code_challenge' => $codeChallenge,
'code_challenge_method' => 'S256',
// 'prompt' => '', // "none", "consent", or "login"
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
Converting Authorization Codes to Access Tokens
If the user approves the authorization request, they will be redirected back to the consuming application. The consumer should verify the state
parameter against the value that was stored prior to the redirect, as in the standard Authorization Code Grant.
If the state parameter matches, the consumer should issue a POST
request to your application to request an access token. The request should include the authorization code that was issued by your application when the user approved the authorization request along with the originally generated code verifier:
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('https://passport-app.test/oauth/token', [
'grant_type' => 'authorization_code',
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'code_verifier' => $codeVerifier,
'code' => $request->code,
]);
return $response->json();
});
Device Authorization Grant
The OAuth2 device authorization grant allows browserless or limited input devices, such as TVs and game consoles, to obtain an access token by exchanging a "device code". When using device flow, the device client will instruct the user to use a secondary device, such as a computer or a smartphone and connect to your server where they will enter the provided "user code" and either approve or deny the access request.
To get started, we need to instruct Passport how to return our "user code" and "authorization" views.
All the authorization view's rendering logic may be customized using the appropriate methods available via the Laravel\Passport\Passport
class. Typically, you should call this method from the boot
method of your application's App\Providers\AppServiceProvider
class.
use Inertia\Inertia;
use Laravel\Passport\Passport;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// By providing a view name...
Passport::deviceUserCodeView('auth.oauth.device.user-code');
Passport::deviceAuthorizationView('auth.oauth.device.authorize');
// By providing a closure...
Passport::deviceUserCodeView(
fn ($parameters) => Inertia::render('Auth/OAuth/Device/UserCode')
);
Passport::deviceAuthorizationView(
fn ($parameters) => Inertia::render('Auth/OAuth/Device/Authorize', [
'request' => $parameters['request'],
'authToken' => $parameters['authToken'],
'client' => $parameters['client'],
'user' => $parameters['user'],
'scopes' => $parameters['scopes'],
])
);
// ...
}
Passport will automatically define routes that return these views. Your auth.oauth.device.user-code
template should include a form that makes a GET request to the passport.device.authorizations.authorize
route. The passport.device.authorizations.authorize
route expects a user_code
query parameter.
Your auth.oauth.device.authorize
template should include a form that makes a POST request to the passport.device.authorizations.approve
route to approve the authorization and a form that makes a DELETE request to the passport.device.authorizations.deny
route to deny the authorization. The passport.device.authorizations.approve
and passport.device.authorizations.deny
routes expect state
, client_id
, and auth_token
fields.
Creating a Device Authorization Grant Client
Before your application can issue tokens via the device authorization grant, you will need to create a device flow enabled client. You may do this using the passport:client
Artisan command with the --device
option. This command will create a first-party device flow enabled client and provide you with a client ID and secret:
php artisan passport:client --device
Additionally, you may use createDeviceAuthorizationGrantClient
method on the ClientRepository
class to register a third-party client that belongs to the given user:
use App\Models\User;
use Laravel\Passport\ClientRepository;
$user = User::find($userId);
$client = app(ClientRepository::class)->createDeviceAuthorizationGrantClient(
user: $user,
name: 'Example Device',
confidential: false,
);
Requesting Tokens
Requesting a Device Code
Once a client has been created, developers may use their client ID to request a device code from your application. First, the consuming device should make a POST
request to your application's /oauth/device/code
route to request a device code:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/device/code', [
'client_id' => 'your-client-id',
'scope' => 'user:read orders:create',
]);
return $response->json();
This will return a JSON response containing device_code
, user_code
, verification_uri
, interval
, and expires_in
attributes. The expires_in
attribute contains the number of seconds until the device code expires. The interval
attribute contains the number of seconds the consuming device should wait between requests when polling /oauth/token
route to avoid rate limit errors.
[!NOTE]
Remember, the/oauth/device/code
route is already defined by Passport. You do not need to manually define this route.
Displaying the Verification URI and User Code
Once a device code request has been obtained, the consuming device should instruct the user to use another device and visit the provided verification_uri
and enter the user_code
in order to approve the authorization request.
Polling Token Request
Since the user will be using a separate device to grant (or deny) access, the consuming device should poll your application's /oauth/token
route to determine when the user has responded to the request. The consuming device should use the minimum polling interval
provided in the JSON response when requesting device code to avoid rate limit errors:
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Sleep;
$interval = 5;
do {
Sleep::for($interval)->seconds();
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'urn:ietf:params:oauth:grant-type:device_code',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // Required for confidential clients only...
'device_code' => 'the-device-code',
]);
if ($response->json('error') === 'slow_down') {
$interval += 5;
}
} while (in_array($response->json('error'), ['authorization_pending', 'slow_down']));
return $response->json();
If the user has approved the authorization request, this will return a JSON response containing access_token
, refresh_token
, and expires_in
attributes. The expires_in
attribute contains the number of seconds until the access token expires.
Password Grant
[!WARNING]
We no longer recommend using password grant tokens. Instead, you should choose a grant type that is currently recommended by OAuth2 Server.
The OAuth2 password grant allows your other first-party clients, such as a mobile application, to obtain an access token using an email address / username and password. This allows you to issue access tokens securely to your first-party clients without requiring your users to go through the entire OAuth2 authorization code redirect flow.
To enable the password grant, call the enablePasswordGrant
method in the boot
method of your application's App\Providers\AppServiceProvider
class:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::enablePasswordGrant();
}
Creating a Password Grant Client
Before your application can issue tokens via the password grant, you will need to create a password grant client. You may do this using the passport:client
Artisan command with the --password
option.
php artisan passport:client --password
Requesting Tokens
Once you have enabled the grant and have created a password grant client, you may request an access token by issuing a POST
request to the /oauth/token
route with the user's email address and password. Remember, this route is already registered by Passport so there is no need to define it manually. If the request is successful, you will receive an access_token
and refresh_token
in the JSON response from the server:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // Required for confidential clients only...
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => 'user:read orders:create',
]);
return $response->json();
[!NOTE]
Remember, access tokens are long-lived by default. However, you are free to configure your maximum access token lifetime if needed.
Requesting All Scopes
When using the password grant or client credentials grant, you may wish to authorize the token for all of the scopes supported by your application. You can do this by requesting the *
scope. If you request the *
scope, the can
method on the token instance will always return true
. This scope may only be assigned to a token that is issued using the password
or client_credentials
grant:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'password',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret', // Required for confidential clients only...
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => '*',
]);
Customizing the User Provider
If your application uses more than one authentication user provider, you may specify which user provider the password grant client uses by providing a --provider
option when creating the client via the artisan passport:client --password
command. The given provider name should match a valid provider defined in your application's config/auth.php
configuration file. You can then protect your route using middleware to ensure that only users from the guard's specified provider are authorized.
Customizing the Username Field
When authenticating using the password grant, Passport will use the email
attribute of your authenticatable model as the "username". However, you may customize this behavior by defining a findForPassport
method on your model:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, Notifiable;
/**
* Find the user instance for the given username.
*/
public function findForPassport(string $username): User
{
return $this->where('username', $username)->first();
}
}
Customizing the Password Validation
When authenticating using the password grant, Passport will use the password
attribute of your model to validate the given password. If your model does not have a password
attribute or you wish to customize the password validation logic, you can define a validateForPassportPasswordGrant
method on your model:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, Notifiable;
/**
* Validate the password of the user for the Passport password grant.
*/
public function validateForPassportPasswordGrant(string $password): bool
{
return Hash::check($password, $this->password);
}
}
Implicit Grant
[!WARNING]
We no longer recommend using implicit grant tokens. Instead, you should choose a grant type that is currently recommended by OAuth2 Server.
The implicit grant is similar to the authorization code grant; however, the token is returned to the client without exchanging an authorization code. This grant is most commonly used for JavaScript or mobile applications where the client credentials can't be securely stored. To enable the grant, call the enableImplicitGrant
method in the boot
method of your application's App\Providers\AppServiceProvider
class:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::enableImplicitGrant();
}
Before your application can issue tokens via the implicit grant, you will need to create an implicit grant client. You may do this using the passport:client
Artisan command with the --implicit
option.
php artisan passport:client --implicit
Once the grant has been enabled and an implicit client has been created, developers may use their client ID to request an access token from your application. The consuming application should make a redirect request to your application's /oauth/authorize
route like so:
use Illuminate\Http\Request;
Route::get('/redirect', function (Request $request) {
$request->session()->put('state', $state = Str::random(40));
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'token',
'scope' => 'user:read orders:create',
'state' => $state,
// 'prompt' => '', // "none", "consent", or "login"
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
[!NOTE]
Remember, the/oauth/authorize
route is already defined by Passport. You do not need to manually define this route.
Client Credentials Grant
The client credentials grant is suitable for machine-to-machine authentication. For example, you might use this grant in a scheduled job which is performing maintenance tasks over an API.
Before your application can issue tokens via the client credentials grant, you will need to create a client credentials grant client. You may do this using the --client
option of the passport:client
Artisan command:
php artisan passport:client --client
Next, assign the Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner
middleware to a route:
use Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner;
Route::get('/orders', function (Request $request) {
// Access token is valid and the client is resource owner...
})->middleware(EnsureClientIsResourceOwner::class);
To restrict access to the route to specific scopes, you may provide a list of the required scopes to the using
method`:
Route::get('/orders', function (Request $request) {
// Access token is valid, the client is resource owner, and has both "servers:read" and "servers:create" scopes...
})->middleware(EnsureClientIsResourceOwner::using('servers:read', 'servers:create');
Retrieving Tokens
To retrieve a token using this grant type, make a request to the oauth/token
endpoint:
use Illuminate\Support\Facades\Http;
$response = Http::asForm()->post('https://passport-app.test/oauth/token', [
'grant_type' => 'client_credentials',
'client_id' => 'your-client-id',
'client_secret' => 'your-client-secret',
'scope' => 'servers:read servers:create',
]);
return $response->json()['access_token'];
Personal Access Tokens
Sometimes, your users may want to issue access tokens to themselves without going through the typical authorization code redirect flow. Allowing users to issue tokens to themselves via your application's UI can be useful for allowing users to experiment with your API or may serve as a simpler approach to issuing access tokens in general.
[!NOTE]
If your application is using Passport primarily to issue personal access tokens, consider using Laravel Sanctum, Laravel's light-weight first-party library for issuing API access tokens.
Creating a Personal Access Client
Before your application can issue personal access tokens, you will need to create a personal access client. You may do this by executing the passport:client
Artisan command with the --personal
option. If you have already run the passport:install
command, you do not need to run this command:
php artisan passport:client --personal
Customizing the User Provider
If your application uses more than one authentication user provider, you may specify which user provider the personal access grant client uses by providing a --provider
option when creating the client via the artisan passport:client --personal
command. The given provider name should match a valid provider defined in your application's config/auth.php
configuration file. You can then protect your route using middleware to ensure that only users from the guard's specified provider are authorized.
Managing Personal Access Tokens
Once you have created a personal access client, you may issue tokens for a given user using the createToken
method on the App\Models\User
model instance. The createToken
method accepts the name of the token as its first argument and an optional array of scopes as its second argument:
use App\Models\User;
use Illuminate\Support\Facades\Date;
use Laravel\Passport\Token;
$user = User::find($userId);
// Creating a token without scopes...
$token = $user->createToken('My Token')->accessToken;
// Creating a token with scopes...
$token = $user->createToken('My Token', ['user:read', 'orders:create'])->accessToken;
// Creating a token with all scopes...
$token = $user->createToken('My Token', ['*'])->accessToken;
// Retrieving all the valid personal access tokens that belong to the user...
$tokens = $user->tokens()
->with('client')
->where('revoked', false)
->where('expires_at', '>', Date::now())
->get()
->filter(fn (Token $token) => $token->client->hasGrantType('personal_access'));
Protecting Routes
Via Middleware
Passport includes an authentication guard that will validate access tokens on incoming requests. Once you have configured the api
guard to use the passport
driver, you only need to specify the auth:api
middleware on any routes that should require a valid access token:
Route::get('/user', function () {
// Only API authenticated users may access this route...
})->middleware('auth:api');
[!WARNING]
If you are using the client credentials grant, you should use theLaravel\Passport\Http\Middleware\EnsureClientIsResourceOwner
middleware to protect your routes instead of theauth:api
middleware.
Multiple Authentication Guards
If your application authenticates different types of users that perhaps use entirely different Eloquent models, you will likely need to define a guard configuration for each user provider type in your application. This allows you to protect requests intended for specific user providers. For example, given the following guard configuration the config/auth.php
configuration file:
'guards' => [
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'api-customers' => [
'driver' => 'passport',
'provider' => 'customers',
],
],
The following route will utilize the api-customers
guard, which uses the customers
user provider, to authenticate incoming requests:
Route::get('/customer', function () {
// ...
})->middleware('auth:api-customers');
[!NOTE]
For more information on using multiple user providers with Passport, please consult the personal access tokens documentation and password grant documentation.
Passing the Access Token
When calling routes that are protected by Passport, your application's API consumers should specify their access token as a Bearer
token in the Authorization
header of their request. For example, when using the Http
Facade:
use Illuminate\Support\Facades\Http;
$response = Http::withHeaders([
'Accept' => 'application/json',
'Authorization' => "Bearer $accessToken",
])->get('https://passport-app.test/api/user');
return $response->json();
Token Scopes
Scopes allow your API clients to request a specific set of permissions when requesting authorization to access an account. For example, if you are building an e-commerce application, not all API consumers will need the ability to place orders. Instead, you may allow the consumers to only request authorization to access order shipment statuses. In other words, scopes allow your application's users to limit the actions a third-party application can perform on their behalf.
Defining Scopes
You may define your API's scopes using the Passport::tokensCan
method in the boot
method of your application's App\Providers\AppServiceProvider
class. The tokensCan
method accepts an array of scope names and scope descriptions. The scope description may be anything you wish and will be displayed to users on the authorization approval screen:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::tokensCan([
'user:read' => 'Retrieve the user info',
'orders:create' => 'Place orders',
'orders:read:status' => 'Check order status',
]);
}
Default Scope
If a client does not request any specific scopes, you may configure your Passport server to attach default scopes to the token using the defaultScopes
method. Typically, you should call this method from the boot
method of your application's App\Providers\AppServiceProvider
class:
use Laravel\Passport\Passport;
Passport::tokensCan([
'user:read' => 'Retrieve the user info',
'orders:create' => 'Place orders',
'orders:read:status' => 'Check order status',
]);
Passport::defaultScopes([
'user:read',
'orders:create',
]);
Assigning Scopes to Tokens
When Requesting Authorization Codes
When requesting an access token using the authorization code grant, consumers should specify their desired scopes as the scope
query string parameter. The scope
parameter should be a space-delimited list of scopes:
Route::get('/redirect', function () {
$query = http_build_query([
'client_id' => 'your-client-id',
'redirect_uri' => 'https://third-party-app.com/callback',
'response_type' => 'code',
'scope' => 'user:read orders:create',
]);
return redirect('https://passport-app.test/oauth/authorize?'.$query);
});
When Issuing Personal Access Tokens
If you are issuing personal access tokens using the App\Models\User
model's createToken
method, you may pass the array of desired scopes as the second argument to the method:
$token = $user->createToken('My Token', ['orders:create'])->accessToken;
Checking Scopes
Passport includes two middleware that may be used to verify that an incoming request is authenticated with a token that has been granted a given scope.
Check For All Scopes
The Laravel\Passport\Http\Middleware\CheckToken
middleware may be assigned to a route to verify that the incoming request's access token has all the listed scopes:
use Laravel\Passport\Http\Middleware\CheckToken;
Route::get('/orders', function () {
// Access token has both "orders:read" and "orders:create" scopes...
})->middleware(['auth:api', CheckToken::using('orders:read', 'orders:create');
Check for Any Scopes
The Laravel\Passport\Http\Middleware\CheckTokenForAnyScope
middleware may be assigned to a route to verify that the incoming request's access token has at least one of the listed scopes:
use Laravel\Passport\Http\Middleware\CheckTokenForAnyScope;
Route::get('/orders', function () {
// Access token has either "orders:read" or "orders:create" scope...
})->middleware(['auth:api', CheckTokenForAnyScope::using('orders:read', 'orders:create');
Checking Scopes on a Token Instance
Once an access token authenticated request has entered your application, you may still check if the token has a given scope using the tokenCan
method on the authenticated App\Models\User
instance:
use Illuminate\Http\Request;
Route::get('/orders', function (Request $request) {
if ($request->user()->tokenCan('orders:create')) {
// ...
}
});
Additional Scope Methods
The scopeIds
method will return an array of all defined IDs / names:
use Laravel\Passport\Passport;
Passport::scopeIds();
The scopes
method will return an array of all defined scopes as instances of Laravel\Passport\Scope
:
Passport::scopes();
The scopesFor
method will return an array of Laravel\Passport\Scope
instances matching the given IDs / names:
Passport::scopesFor(['user:read', 'orders:create']);
You may determine if a given scope has been defined using the hasScope
method:
Passport::hasScope('orders:create');
SPA Authentication
When building an API, it can be extremely useful to be able to consume your own API from your JavaScript application. This approach to API development allows your own application to consume the same API that you are sharing with the world. The same API may be consumed by your web application, mobile applications, third-party applications, and any SDKs that you may publish on various package managers.
Typically, if you want to consume your API from your JavaScript application, you would need to manually send an access token to the application and pass it with each request to your application. However, Passport includes a middleware that can handle this for you. All you need to do is append the CreateFreshApiToken
middleware to the web
middleware group in your application's bootstrap/app.php
file:
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
CreateFreshApiToken::class,
]);
})
[!WARNING]
You should ensure that theCreateFreshApiToken
middleware is the last middleware listed in your middleware stack.
This middleware will attach a laravel_token
cookie to your outgoing responses. This cookie contains an encrypted JWT that Passport will use to authenticate API requests from your JavaScript application. The JWT has a lifetime equal to your session.lifetime
configuration value. Now, since the browser will automatically send the cookie with all subsequent requests, you may make requests to your application's API without explicitly passing an access token:
axios.get('/api/user')
.then(response => {
console.log(response.data);
});
Customizing the Cookie Name
If needed, you can customize the laravel_token
cookie's name using the Passport::cookie
method. Typically, this method should be called from the boot
method of your application's App\Providers\AppServiceProvider
class:
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Passport::cookie('custom_name');
}
CSRF Protection
When using this method of authentication, you will need to ensure a valid CSRF token header is included in your requests. The default Laravel JavaScript scaffolding included with the skeleton application and all starter kits includes an Axios instance, which will automatically use the encrypted XSRF-TOKEN
cookie value to send an X-XSRF-TOKEN
header on same-origin requests.
[!NOTE]
If you choose to send theX-CSRF-TOKEN
header instead ofX-XSRF-TOKEN
, you will need to use the unencrypted token provided bycsrf_token()
.
Events
Passport raises events when issuing access tokens and refresh tokens. You may listen for these events to prune or revoke other access tokens in your database:
| Event Name | | --- | | `Laravel\Passport\Events\AccessTokenCreated` | | `Laravel\Passport\Events\AccessTokenRevoked` | | `Laravel\Passport\Events\RefreshTokenCreated` |
Testing
Passport's actingAs
method may be used to specify the currently authenticated user as well as its scopes. The first argument given to the actingAs
method is the user instance and the second is an array of scopes that should be granted to the user's token:
use App\Models\User;
use Laravel\Passport\Passport;
test('orders can be created', function () {
Passport::actingAs(
User::factory()->create(),
['orders:create']
);
$response = $this->post('/api/orders');
$response->assertStatus(201);
});
use App\Models\User;
use Laravel\Passport\Passport;
public function test_orders_can_be_created(): void
{
Passport::actingAs(
User::factory()->create(),
['orders:create']
);
$response = $this->post('/api/orders');
$response->assertStatus(201);
}
Passport's actingAsClient
method may be used to specify the currently authenticated client as well as its scopes. The first argument given to the actingAsClient
method is the client instance and the second is an array of scopes that should be granted to the client's token:
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
test('servers can be retrieved', function () {
Passport::actingAsClient(
Client::factory()->create(),
['servers:read']
);
$response = $this->get('/api/servers');
$response->assertStatus(200);
});
use Laravel\Passport\Client;
use Laravel\Passport\Passport;
public function test_servers_can_be_retrieved(): void
{
Passport::actingAsClient(
Client::factory()->create(),
['servers:read']
);
$response = $this->get('/api/servers');
$response->assertStatus(200);
}
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: