Laravelのpassportでgrant_typeを追加する

Laravelのpassportで既存のgrantをカスタマイズ、追加する必要があったので、手順をメモ
※ 各種追加しているファイルのディレクトリは自信がないので指摘ください

# カスタムGrant追加

AbstractGrantを実装したクラスを作成

respondToAccessTokenRequestメソッドを独自に実装する必要がある
中身はPasswordGrantをコピーしてgetIdentifierのみ変更している

```php:app/Providers/OAuth2/Server/Grant/CustomGrant.php
<?php

namespace App\Providers\OAuth2\Server\Grant;

use League\OAuth2\Server\Grant\AbstractGrant;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use Psr\Http\Message\ServerRequestInterface;

class CustomGrant extends AbstractGrant {

    /**
     * @param UserRepositoryInterface         $userRepository
     * @param RefreshTokenRepositoryInterface $refreshTokenRepository
     */
    public function __construct(
        UserRepositoryInterface $userRepository,
        RefreshTokenRepositoryInterface $refreshTokenRepository
    ) {
        $this->setUserRepository($userRepository);
        $this->setRefreshTokenRepository($refreshTokenRepository);

        $this->refreshTokenTTL = new \DateInterval('P1M');
    }

    /**
     * {@inheritdoc}
     */
    public function respondToAccessTokenRequest(
        ServerRequestInterface $request,
        ResponseTypeInterface $responseType,
        \DateInterval $accessTokenTTL
    ) {
        // Validate request
        $client = $this->validateClient($request);
        $scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
        $user = $this->validateUser($request, $client);

        // Finalize the requested scopes
        $scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());

        // Issue and persist new tokens
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $scopes);
        $refreshToken = $this->issueRefreshToken($accessToken);

        // Inject tokens into response
        $responseType->setAccessToken($accessToken);
        $responseType->setRefreshToken($refreshToken);

        return $responseType;
    }

    /**
     * @param ServerRequestInterface $request
     * @param ClientEntityInterface  $client
     *
     * @throws OAuthServerException
     *
     * @return UserEntityInterface
     */
    protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
    {
        $username = $this->getRequestParameter('username', $request);
        if (is_null($username)) {
            throw OAuthServerException::invalidRequest('username');
        }

        $password = $this->getRequestParameter('password', $request);
        if (is_null($password)) {
            throw OAuthServerException::invalidRequest('password');
        }

        $user = $this->userRepository->getUserEntityByUserCredentials(
            $username,
            $password,
            $this->getIdentifier(),
            $client
        );
        if ($user instanceof UserEntityInterface === false) {
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));

            throw OAuthServerException::invalidCredentials();
        }

        return $user;
    }

    /**
     * {@inheritdoc}
     */
    public function getIdentifier()
    {
        // POSTのgrant_typeで指定する名称になる
        return 'custom';
    }
}
```
# PassportServiceProvider拡張

PassportServiceProviderを継承してカスタムPassportServiceProviderを作成
registerAuthorizationServerメソッドをオーバーライドし、作成したカスタムGrantの呼び出しを追加する

```php:app/Providers/CustomPassportServiceProvider.php
<?php

namespace App\Providers;

use DateInterval;
use Laravel\Passport\Passport;
use Laravel\Passport\PassportServiceProvider;
use League\OAuth2\Server\AuthorizationServer;
use Laravel\Passport\Bridge\PersonalAccessGrant;
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
use App\Providers\OAuth2\Server\Grant\CustomGrant;

class CustomPassportServiceProvider extends PassportServiceProvider
{
    /**
     * Register the authorization server.
     *
     * @return void
     */
    protected function registerAuthorizationServer()
    {
        $this->app->singleton(AuthorizationServer::class, function () {
            return tap($this->makeAuthorizationServer(), function ($server) {
                $server->enableGrantType(
                    $this->makeAuthCodeGrant(), Passport::tokensExpireIn()
                );

                $server->enableGrantType(
                    $this->makeRefreshTokenGrant(), Passport::tokensExpireIn()
                );

                $server->enableGrantType(
                    $this->makePasswordGrant(), Passport::tokensExpireIn()
                );

                $server->enableGrantType(
                    new PersonalAccessGrant, new DateInterval('P1Y')
                );

                // 追加したカスタムGrantの呼び出し
                $server->enableGrantType(
                   $this->makeCustomGrant(), Passport::tokensExpireIn()
                );

                $server->enableGrantType(
                    new ClientCredentialsGrant, Passport::tokensExpireIn()
                );

                if (Passport::$implicitGrantEnabled) {
                    $server->enableGrantType(
                        $this->makeImplicitGrant(), Passport::tokensExpireIn()
                    );
                }
            });
        });
    }

    /**
     * Create and configure a Password grant instance.
     *
     * @return \App\Providers\OAuth2\Server\Grant\CustomGrant
     */
    protected function makeCustomGrant()
    {
        $grant = new CustomGrant(
            $this->app->make(\Laravel\Passport\Bridge\UserRepository::class),
            $this->app->make(\Laravel\Passport\Bridge\RefreshTokenRepository::class)
        );

        $grant->setRefreshTokenTTL(Passport::refreshTokensExpireIn());

        return $grant;
    }
}
```

# 登録するサービスプロバイダーの変更

PassportServiceProviderを継承して作成したCustomPassportServiceを登録する

```php:config/app.php
          /* Package Service Providers... */
          Laravel\Tinker\TinkerServiceProvider::class,		          
          Laravel\Tinker\TinkerServiceProvider::class,
          //Laravel\Passport\PassportServiceProvider::class,
          App\Providers\CustomPassportServiceProvider::class,
```
# OAuthトークン取得

上記を実装することでカスタムGrantでOAuthのトークンを取得できる

アクセスURL
http://ドメイン/oauth/token

POSTパラメータ

```json
{
    "client_id": "XX",
    "client_secret": "XXXXXXXXXXXXXXXXXXXXXXX",
    "username": "XXXXXXXXXXXXXXXXXXXXXXX",
    "password": "XXXXXXXXXXXXXXXXXXXXXXX",
    "grant_type": "custom",
    "scope": ""
}
```

もっとこうした方がいい、ここ間違ってる等あれば指摘お願いします。