Laravel Passport

简介

在 Laravel 中,实现基于传统表单的登陆和授权已经非常简单,但是如何满足 API 场景下的授权需求呢?在 API 场景里通常通过令牌来实现用户授权,而非维护请求之间的 Session 状态。在 Laravel 项目中使用 Passport 可以轻而易举地实现 API 授权认证,Passport 可以在几分钟之内为你的应用程序提供完整的 OAuth2 服务端实现。Passport 是基于由 Andy Millington 和 Simon Hamp 维护的 League OAuth2 server 建立的。

注意:本文档假定你已熟悉 OAuth2 。如果你并不了解 OAuth2 ,阅读之前请先熟悉下 OAuth2 的 常用术语 和特性。

安装

在开始之前,请通过 Composer 包管理器安装 Passport:

  1. composer require laravel/passport

Passport 服务提供器使用框架注册自己的数据库迁移目录,因此在注册提供器后,就应该运行 Passport 的迁移命令来自动创建存储客户端和令牌的数据表:

  1. php artisan migrate

接下来,运行 passport:install 命令来创建生成安全访问令牌时所需的加密密钥,同时,这条命令也会创建用于生成访问令牌的「个人访问」客户端和「密码授权」客户端:

  1. php artisan passport:install

上面命令执行后,请将 Laravel\Passport\HasApiTokens Trait 添加到 App\User 模型中,这个 Trait 会给你的模型提供一些辅助函数,用于检查已认证用户的令牌和使用范围:

  1. <?php
  2. namespace App;
  3. use Illuminate\Foundation\Auth\User as Authenticatable;
  4. use Illuminate\Notifications\Notifiable;
  5. use Laravel\Passport\HasApiTokens;
  6. class User extends Authenticatable
  7. {
  8. use HasApiTokens, Notifiable;
  9. }

接下来,在 AuthServiceProviderboot 方法中调用 Passport::routes 函数。这个函数会注册发出访问令牌并撤销访问令牌、客户端和个人访问令牌所必需的路由:

  1. <?php
  2. namespace App\Providers;
  3. use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
  4. use Illuminate\Support\Facades\Gate;
  5. use Laravel\Passport\Passport;
  6. class AuthServiceProvider extends ServiceProvider
  7. {
  8. /**
  9. * 应用程序的策略映射
  10. *
  11. * @var array
  12. */
  13. protected $policies = [
  14. 'App\Model' => 'App\Policies\ModelPolicy',
  15. ];
  16. /**
  17. * 注册认证 / 授权服务
  18. *
  19. * @return void
  20. */
  21. public function boot()
  22. {
  23. $this->registerPolicies();
  24. Passport::routes();
  25. }
  26. }

最后,将配置文件 config/auth.php 中授权看守器 guardsapidriver 选项改为 passport。此调整会让你的应用程序在在验证传入的 API 的请求时使用 Passport 的 TokenGuard 来处理:

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],

    'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],

自定义迁移

如果你不打算使用 Passport 的默认迁移,你应该在 AppServiceProviderregister 方法中调用 Passport::ignoreMigrations 方法。 你可以用这个命令 php artisan vendor:publish —tag=passport-migrations 导出默认迁移。

默认情况下,Passport 使用字段 user_id 标识用户。如果想使用不同的字段标识用户 (例如:UUIDs),可以修改默认的 Passport 迁移文件。

前端快速上手

注意:为了使用 Passport 的 Vue 组件,你必须使用 Vue JavaScript 框架。这些组件也使用了 Bootstrap CSS 框架。然而,如果你不打算使用这些工具,这些组件对于你自己的前端组件编写也十分有价值。

Passport 提供了一系列 JSON API ,你可以用它们来允许你的用户创建客户端和个人访问令牌。然而,编写与这些 API 交互的前端代码可能是很占用时间的。因此,Passport 也包括了预编译的 Vue 组件,你可以直接使用或将其作为你自己的前端参考。

要使用 Passport 的 Vue 组件,使用 vendor:publish Artisan 命令:

php artisan vendor:publish --tag=passport-components

被发布的组件将会被放到 resources/js/components 目录下。当组件被发布后,你应该在你的 resources/js/app.js 文件中注册它们:

Vue.component(
    'passport-clients',
    require('./components/passport/Clients.vue').default
);

Vue.component(
    'passport-authorized-clients',
    require('./components/passport/AuthorizedClients.vue').default
);

Vue.component(
    'passport-personal-access-tokens',
    require('./components/passport/PersonalAccessTokens.vue').default
);

注意:在 Laravel v5.7.19 之前,在注册组件时添加 .default 会导致控制台错误。有关此更改的解释,请参阅 Laravel Mix v4.0.0 发布说明

在注册了组件后,请确保运行 npm run dev 来重新编译你的资源。 当你重编译你的资源后,你可以将组件放到你应用的模板中以开始创建客户端和个人访问令牌:

<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens>

部署 Passport

第一次在你的生产环境部署 Passport 时,你大概需要运行 passport:keys 命令。这个命令生成 Passport 生成访问令牌所需的密钥。生成的密钥一般情况下不应放在版本控制中:

php artisan passport:keys

可以使用 Passport::loadKeysFrom 方法来自定义 Passport 密钥的加载路径:

/**
 * 注册认证 / 授权服务
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::loadKeysFrom('/secret-keys/oauth');
}

另外,您也可以使用 php artisan vendor:publish —tag=passport-config 发布 Passport 的配置文件,然后从您的环境变量提供加载加密密钥的选项:

PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----
<此处填写您的私钥>
-----END RSA PRIVATE KEY-----"

PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
<此处填写您的公钥>
-----END PUBLIC KEY-----"

配置

令牌的有效期

默认情况下,Passport 发放的访问令牌是有一年有效期的。但是如果你想自定义访问令牌的有效期,可以使用 tokensExpireInrefreshTokensExpireInpersonalAccessTokensExpireIn 方法。上述两个方法同样需要在 AuthServiceProviderboot 方法中调用:

/**
 * 注册认证 / 授权服务
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::tokensExpireIn(now()->addDays(15));

    Passport::refreshTokensExpireIn(now()->addDays(30));

    Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}

覆盖默认模型

可以自由扩展 Passport 使用的模型:

use Laravel\Passport\Client as PassportClient;

class Client extends PassportClient
{
    // ...
}

然后,您可以通过 Passport 类自定义模型覆盖默认模型

use App\Models\Passport\AuthCode;
use App\Models\Passport\Client;
use App\Models\Passport\PersonalAccessClient;
use App\Models\Passport\Token;

/**
 * 注册认证 / 授权服务
 *
 * @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);
}

发放访问令牌

熟悉 OAuth2 的开发者一定知道, OAuth2 中必不可少的部分就是授权码。当使用授权码时,客户端应用程序会将用户重定向到你的服务器,他们将批准或拒绝向客户端发出访问令牌的请求。

管理客户端

首先,构建需要与应用程序 API 交互的应用程序,开发人员将需要通过创建一个「客户端」来注册自己的应用程序。一般来说,这包括在用户批准其授权请求后,提供其应用程序的名称和应用程序可以重定向到的 URL。

passport:client 命令

创建客户端最简单的方式是使用 Artisan 命令 passport:client,你可以使用此命令创建自己的客户端,用于测试你的 OAuth2 的功能。在你执行 client 命令时,Passport 会提示你输入有关客户端的信息,最终会给你提供客户端的 ID 和 密钥:

php artisan passport:client

Redirect URLs

当有多个重定向 URL 白名单时,可以在 passport:client 命令提示输入 URL 时,使用逗号分隔来指定:

http://example.com/callback,http://examplefoo.com/callback

注意:任何包含逗号的 URL 都必须进行编码。

JSON API

考虑到你的用户无法使用 client 命令,Passport 为此提供了可用于创建「客户端」的 JSON API。这样你就不用再花时间编写控制器来创建、更新和删除客户端。

然而,你仍旧需要基于 Passport 的 JSON API 开发一套前端界面,为你的用户提供管理客户端的面板。下面我们会列出所有用于管理客户端的 API,为了方便起见,我们使用 Axios 来演示对端口发出 HTTP 请求。

这个 JSON API 由 webauth 两个中间件保护,所以只能从应用程序中调用,不能从外部调用。

Tip:如果你不想自己实现整个客户端管理的前端界面,可以使用 前端快速上手 在几分钟内组建一套功能齐全的前端界面。

GET /oauth/clients

此路由会返回认证用户的所有客户端。主要用途是列出所有用户的客户端,以便他们可以编辑或删除它们:

axios.get('/oauth/clients')
    .then(response => {
        console.log(response.data);
    });

POST /oauth/clients

此路由用于创建新客户端。它需要两个参数:客户端的名称 name 和授权后回调的 URL redirect。在批准或拒绝授权请求后,用户会被重定向到 redirect 参数提供的链接。

当客户端创建后,会返回客户端的 ID 和密钥。客户端可以使用这两个值从你的授权服务请求访问令牌 (Access token) 。该路由会返回新的客户端实例:

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}

此路由用于更新客户端信息。它需要两个参数:客户端的名称 name 和授权后回调的 URL redirect。在批准或拒绝授权请求后,用户会被重定向 redirect 到这个链接。此路由会返回更新后的客户端实例:

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}

此路由用于删除客户端 (client):

axios.delete('/oauth/clients/' + clientId)
    .then(response => {
        //
    });

请求令牌

授权时的重定向

客户端创建之后,开发者会使用此客户端的 ID 和密钥来请求授权代码,并从应用程序访问令牌。首先,接入应用的用户向你应用程序的 /oauth/authorize 路由发出重定向请求,示例如下:

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://example.com/callback',
        'response_type' => 'code',
        'scope' => '',
        'state' => $state,
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});

备注:注意,路由 /oauth/authorize 已经在 Passport::routes 方法中定义。你不需要手动定义此路由。

批准请求

接收到授权请求时,Passport 会自动向用户显示一个模版页面,允许用户批准或拒绝授权请求。如果用户批准请求,他们会被重定向回接入的应用程序指定的 redirect_uriredirect_uri 必须和客户端创建时指定的 redirect 链接完全一致。

如果你想自定义授权确认页面,可以使用 Artisan 命令 vendor:publish 发布 Passport 的视图。发布后的视图文件存放在 resources/views/vendor/passport

php artisan vendor:publish --tag=passport-views

有时您可能希望跳过授权提示,例如授权第一方客户端时。 您可以通过在客户端模型上定义 skipsAuthorization 方法来实现此目的。 如果 skipsAuthorization 返回 true,客户端将被批准,用户将被立即重定向回 redirect_uri

<?php

namespace App\Models\Passport;

use Laravel\Passport\Client as BaseClient;

class Client extends BaseClient
{
    /**
     * 确定客户端是否应跳过授权提示
     *
     * @return bool
     */
    public function skipsAuthorization()
    {
        return $this->firstParty();
    }
}

将授权码转换为访问令牌

如果用户批准授权请求,他们将被重定向回应用程序。 应首先根据重定向之前存储的值验证 state 参数。 如果 state 参数匹配,则应向您的应用程序发出 POST 请求以请求访问令牌。 该请求应包括用户批准授权请求时由应用程序颁发的授权代码。 在下面的例子中,我们将使用 Guzzle HTTP 库来实现这次 POST 请求:

Route::get('/callback', function (Request $request) {
    $state = $request->session()->pull('state');

    throw_unless(
        strlen($state) > 0 && $state === $request->state,
        InvalidArgumentException::class
    );

    $http = new GuzzleHttp\Client;

    $response = $http->post('http://your-app.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => 'client-id',
            'client_secret' => 'client-secret',
            'redirect_uri' => 'http://example.com/callback',
            'code' => $request->code,
        ],
    ]);

    return json_decode((string) $response->getBody(), true);
});

路由 /oauth/token 返回的 JSON 响应中会包含 access_tokenrefresh_tokenexpires_in 属性。expires_in 属性包含访问令牌的有效期(单位:秒)。

备注:像 /oauth/authorize 路由一样,/oauth/token 路由在 Passport::routes 方法中定义了,你没必要手动去定义它。默认情况下,此路由使用 ThrottleRequests 中间件的设置进行限流。

刷新令牌

如果你的应用程序发放了短期的访问令牌,用户将需要通过在发出访问令牌时提供给他们的刷新令牌来刷新其访问令牌。在下面的例子中,我们使用 Guzzle HTTP 库来刷新令牌:

$http = new GuzzleHttp\Client;

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'refresh_token',
        'refresh_token' => 'the-refresh-token',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'scope' => '',
    ],
]);

return json_decode((string) $response->getBody(), true);

路由 /oauth/token 会返回一个 JSON 响应,其中包含 access_tokenrefresh_tokenexpires_in 属性。expires_in 属性包含访问令牌的有效时间(单位:秒)。

清除令牌

当令牌被撤销或过期时,您可能希望从数据库中清除它们。artisan Passport 命令可以为你做这件事

# 清除已撤销和过期的令牌和验证码...
php artisan passport:purge

# 只清除被撤销的令牌和验证码...
php artisan passport:purge --revoked 

# 只清除过期的令牌和验证码...
php artisan passport:purge --expired

您还可以在您的控制台 Kernel 类中配置一个调度任务来自动操作您的令牌:

/**
 * 定义应用程序的调度任务
 *
 * @param  \Illuminate\Console\Scheduling\Schedule  $schedule
 * @return void
 */
protected function schedule(Schedule $schedule)
{
    $schedule->command('passport:purge')->hourly();
}

PECK 授权认证码

带有「代码交换验证密钥」(PKCE)的授权代码授权是对访问API的单页应用程序或本机应用程序进行身份验证的一种安全方法。当您不能保证客户端秘密将被秘密存储,或者为了减少授权代码被攻击者截获的威胁时,应该使用此授权。「代码验证器」 和 「代码挑战」的组合将在为访问令牌交换授权代码时替换客户端机密。

创建客户端

在使用 PKCE 的授权码令牌之前,您需要创建一个启用了 PKCE 的客户机。您可以使用 passport:client 命令和 —public 选项来完成此操作:

php artisan passport:client --public

Requesting Tokens

Code Verifier & 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 "-", ".", "_", "~", 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', $code_verifier, 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:

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://example.com/callback',
        'response_type' => 'code',
        'scope' => '',
        'state' => $state,
        'code_challenge' => $codeChallenge,
        'code_challenge_method' => 'S256',
    ]);

    return redirect('http://your-app.com/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:

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 = (new GuzzleHttp\Client)->post('http://your-app.com/oauth/token', [
        'form_params' => [
            'grant_type' => 'authorization_code',
            'client_id' => 'client-id',
            'redirect_uri' => 'http://example.com/callback',
            'code_verifier' => $codeVerifier,
            'code' => $request->code,
        ],
    ]);

    return json_decode((string) $response->getBody(), true);
});

Password Grant Tokens

The OAuth2 password grant allows your other first-party clients, such as a mobile application, to obtain an access token using an e-mail 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.

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 command with the —password option. If you have already run the passport:install command, you do not need to run this command:

php artisan passport:client --password

Requesting Tokens

Once you 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 the Passport::routes method 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:

$http = new GuzzleHttp\Client;

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => 'taylor@laravel.com',
        'password' => 'my-password',
        'scope' => '',
    ],
]);

return json_decode((string) $response->getBody(), true);

Tip: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:

$response = $http->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => 'taylor@laravel.com',
        'password' => 'my-password',
        'scope' => '*',
    ],
]);

自定义用户名字段

当使用密码验证时,Passport会在模型中使用email属性作为「username」。不过,你仍然可以通过在模型中定义findFotPassport方法来自定义验证行为:

<?php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, Notifiable;

    /**
     * 通过给定的username获取用户实例
     *
     * @param  string  $username
     * @return \App\User
     */
    public function findForPassport($username)
    {
        return $this->where('username', $username)->first();
    }
}

自定义密码验证

当使用密码进行验证时,Passport将使用模型中password属性值验证给定的密码。如果你的模型没有password属性或者你希望自定义密码验证的逻辑,你可以在模型中定义一个validateForPassportPasswordGrant方法来实现:

<?php

namespace App;

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;

    /**
     * 通过Passport的密码授权验证用户使用的密码。
     *
     * @param  string  $password
     * @return bool
     */
    public function validateForPassportPasswordGrant($password)
    {
        return Hash::check($password, $this->password);
    }
}

隐式授权令牌

隐式授权类似于授权码授权;但是,令牌将在不交换授权码的情况下返回给客户端。这种授权最常用于无法安全存储客户端凭据的 javascript 或移动应用程序。通过调用 AuthServiceProvider 中的 enableImplicitGrant 方法来启用这种授权:

/**
 * 注册任何身份验证/授权服务。
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::enableImplicitGrant();
}

启用上述授权后,开发者们可以使用他们的客户端 ID 从你的应用程序请求访问令牌。接入的应用程序应该向你的应用程序的 /oauth/authorize 路由发出重定向请求,如下所示:

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://example.com/callback',
        'response_type' => 'token',
        'scope' => '',
        'state' => $state,
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});

{提示} 请记住,/oauth/authorize 路由已经在 Passport::routes 方法中定义好了,所以无需再次手动定义此路由。

客户端凭证授予令牌

客户端凭证授予令牌适用于计算机到计算机的身份验证。例如,你可以在通过 API 执行维护任务的计划作业中使用此授权。

在应用程序可以通过客户端凭证授予令牌之前,需要先创建一个客户端凭证授权的客户端。你可以通过 passport:client 命令行的 —client 选项来完成此操作:

php artisan passport:client --client

接下来,要使用这种授权,你首先需要在 app/Http/Kernel.php$routeMiddleware 属性中添加 CheckClientCredentials 中间件:

use Laravel\Passport\Http\Middleware\CheckClientCredentials;

protected $routeMiddleware = [
    'client' => CheckClientCredentials::class,
];

之后,在路由上附加中间件:

Route::get('/orders', function (Request $request) {
    ...
})->middleware('client');

要将对路由的访问限制在特定作用域内,可以在将 client 中间件附加到路由并提供以逗号分隔的所需作用域列表:

Route::get('/orders', function (Request $request) {
    ...
})->middleware('client:check-status,your-scope');

Retrieving Tokens

To retrieve a token using this grant type, make a request to the oauth/token endpoint:

$guzzle = new GuzzleHttp\Client;

$response = $guzzle->post('http://your-app.com/oauth/token', [
    'form_params' => [
        'grant_type' => 'client_credentials',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'scope' => 'your-scope',
    ],
]);

return json_decode((string) $response->getBody(), true) ['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.

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 using the passport:client 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

If you have already defined a personal access client, you may instruct Passport to use it using the personalAccessClientId method. Typically, this method should be called from the boot method of your AuthServiceProvider:

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::personalAccessClientId('client-id');
}

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 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:

$user = App\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;

JSON API

Passport also includes a JSON API for managing personal access tokens. You may pair this with your own frontend to offer your users a dashboard for managing personal access tokens. Below, we'll review all of the API endpoints for managing personal access tokens. For convenience, we'll use Axios to demonstrate making HTTP requests to the endpoints.

The JSON API is guarded by the web and auth middleware; therefore, it may only be called from your own application. It is not able to be called from an external source.

Tip:If you don't want to implement the personal access token frontend yourself, you can use the frontend quickstart to have a fully functional frontend in a matter of minutes.

GET /oauth/scopes

This route returns all of the scopes defined for your application. You may use this route to list the scopes a user may assign to a personal access token:

axios.get('/oauth/scopes')
    .then(response => {
        console.log(response.data);
    });

GET /oauth/personal-access-tokens

This route returns all of the personal access tokens that the authenticated user has created. This is primarily useful for listing all of the user's tokens so that they may edit or delete them:

axios.get('/oauth/personal-access-tokens')
    .then(response => {
        console.log(response.data);
    });

POST /oauth/personal-access-tokens

This route creates new personal access tokens. It requires two pieces of data: the token's name and the scopes that should be assigned to the 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}

This route may be used to delete personal access tokens:

axios.delete('/oauth/personal-access-tokens/' + tokenId);

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 require a valid access token:

Route::get('/user', function () {
    //
})->middleware('auth:api');

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 Guzzle HTTP library:

$response = $client->request('GET', '/api/user', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer '.$accessToken,
    ],
]);

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 AuthServiceProvider. 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:

use Laravel\Passport\Passport;

Passport::tokensCan([
    'place-orders' => 'Place orders',
    'check-status' => 'Check order status',
]);

Default Scope

If a client does not request any specific scopes, you may configure your Passport server to attach a default scope to the token using the setDefaultScope method. Typically, you should call this method from the boot method of your AuthServiceProvider:

use Laravel\Passport\Passport;

Passport::setDefaultScope([
    'check-status',
    'place-orders',
]);

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' => 'client-id',
        'redirect_uri' => 'http://example.com/callback',
        'response_type' => 'code',
        'scope' => 'place-orders check-status',
    ]);

    return redirect('http://your-app.com/oauth/authorize?'.$query);
});

When Issuing Personal Access Tokens

If you are issuing personal access tokens using the 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', ['place-orders'])->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. To get started, add the following middleware to the $routeMiddleware property of your app/Http/Kernel.php file:

'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,

Check For All Scopes

The scopes middleware may be assigned to a route to verify that the incoming request's access token has all of the listed scopes:

Route::get('/orders', function () {
    // Access token has both "check-status" and "place-orders" scopes...
})->middleware(['auth:api', 'scopes:check-status,place-orders']);

Check For Any Scopes

The scope middleware may be assigned to a route to verify that the incoming request's access token has at least one of the listed scopes:

Route::get('/orders', function () {
    // Access token has either "check-status" or "place-orders" scope...
})->middleware(['auth:api', 'scope:check-status,place-orders']);

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 User instance:

use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    if ($request->user()->tokenCan('place-orders')) {
        //
    }
});

Additional Scope Methods

The scopeIds method will return an array of all defined IDs / names:

Laravel\Passport\Passport::scopeIds();

The scopes method will return an array of all defined scopes as instances of Laravel\Passport\Scope:

Laravel\Passport\Passport::scopes();

The scopesFor method will return an array of Laravel\Passport\Scope instances matching the given IDs / names:

Laravel\Passport\Passport::scopesFor(['place-orders', 'check-status']);

You may determine if a given scope has been defined using the hasScope method:

Laravel\Passport\Passport::hasScope('place-orders');

Consuming Your API With JavaScript

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 add the CreateFreshApiToken middleware to your web middleware group in your app/Http/Kernel.php file:

'web' => [
    // Other middleware...
    \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],

注意:You should ensure that the CreateFreshApiToken middleware is the last middleware listed in your middleware stack.

This Passport 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. Now, 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 AuthServiceProvider:

/**
 * Register any authentication / authorization services.
 *
 * @return void
 */
public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    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 includes an Axios instance, which will automatically use the encrypted XSRF-TOKEN cookie value to send a X-XSRF-TOKEN header on same-origin requests.

Tip:If you choose to send the X-CSRF-TOKEN header instead of X-XSRF-TOKEN, you will need to use the unencrypted token provided by csrf_token().

Events

Passport raises events when issuing access tokens and refresh tokens. You may use these events to prune or revoke other access tokens in your database. You may attach listeners to these events in your application's EventServiceProvider:

/**
 * 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',
    ],
];

测试

Passport 的 actingAs 方法可以指定当前已认证用户及其作用域。 actingAs 方法的第一个参数是用户实例,第二个参数是用户令牌作用域数组:

use App\User;
use Laravel\Passport\Passport;

public function testServerCreation()
{
    Passport::actingAs(
        factory(User::class)->create(),
        ['create-servers']
    );

    $response = $this->post('/api/create-server');

    $response->assertStatus(201);
}

Passport 的 actingAsClient 方法可以指定当前已认证用户及其作用域。 actingAsClient 方法的第一个参数是用户实例,第二个参数是用户令牌作用域数组:

use Laravel\Passport\Client;
use Laravel\Passport\Passport;

public function testGetOrders()
{
    Passport::actingAsClient(
        factory(Client::class)->create(),
        ['check-status']
    );

    $response = $this->get('/api/orders');

    $response->assertStatus(200);
}

本文章首发在 LearnKu.com 网站上。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接 我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

Laravel China 社区:https://learnku.com/docs/laravel/7.x/passport/7515