web-dev-qa-db-ja.com

Laravelファーストパーティアプリのパスポート付与フロー

私は Laravel Passport を使用して、APIの一部にサードパーティのアプリへのアクセスを許可しています。

しかし、私は自分のファーストパーティNative Android Appを介して自分のAPIも使用しています。そのため、この場合のベストプラクティスについてインターネット全体を調べましたが、結論。

ここに私が見つけた可能性があります:

可能性#01

User Credential Password Grant Flow に従います。
この場合、client_secretおよびclient_idを許可サーバーに送信します。それらを安全に保つために、モバイルアプリケーションのソースコードにそれらを書き込むことはできません(APKはコンパイルできません...)。

したがって、私には2つの選択肢があります。

可能性#01-選択肢A

oauthエンドポイントを呼び出す前に、自分のサーバーを介してプロキシ化し、シークレットを挿入します。

$proxy = Request::create('/oauth/token', 'post', [
    'grant_type' => 'password',
    'client_id' => 1,
    'client_secret' => 'myownclientsecretishere',
    'username' => $username,
    'password' => $password
]);
$proxy->headers->set('Accept', 'application/json');
$response = app()->handle($proxy);

可能性#01-選択肢B

ミドルウェアを使用してoauthエンドポイントを呼び出すときにシークレットを挿入します。

class InjectPasswordGrantSecret
{
    public function handle($request, Closure $next)
    {
        $request->request->add([
            'client_id' => 1,
            'client_secret' => 'myownclientsecretishere'
        ]);
        return $next($request);
    }
}

これらは実際の例ですが、リソースにも貪欲です。ローカルマシンでApacheベンチマークを使用しようとしたところ、9リクエスト/第二。

可能性#02

Personal Access Grant をフォローできます。
これは OAuth2 の標準のようには見えません。これにより、次のようにカスタムルートを介してトークンを作成できます。

if (! auth()->attempt(compact('username', 'password'))) {
    return error_response(__('auth.failed'));
}
$user = auth()->user();
$token = $user->createToken(null)->accessToken;

Apacheベンチマークを使用すると、より良い結果が得られます(30リクエスト/秒など)。

ただし、トークンの有効期間はデフォルトでは構成できず、1年に設定されています(カスタムプロバイダーを使用してこの有効期間を構成できるようにする回避策があることに注意してください)。

このソリューションが本番環境で使用されることを意図しているかどうか、私は本当に疑問に思っています。

最初は自分のアプリしか持っていなかったため、 JWT tymon ライブラリーを使用しました。しかし、私はそれをファーストパーティとサードパーティのアプリで動作させる必要があるので、OAuth2(Laravel Passport)が良い解決策になると思いました...

私は誰かがこれを手伝ってくれることを望み、それを本番サーバーで安全にそして[ゆっくりと]動作させるための良い解決策となることができるものを説明します。

13
Marc

OAuth2は標準ですが、人が行う実装は異なる場合がありますが、ほとんどの場合、最終的には何も変更されません。
たとえば、ルートパスとして_oauth/token_または_myserver/mytokenroute_を使用しても、トークンの生成が同じ方法で行われている限り、ルートが行うことに何も変更されません。別の例として、_client_secret_がルートで必要かどうかは、それが安全に提供されている(クライアント側に格納されていない)場合は重要ではありません。

したがって、OAuth2フローを適切に制御する必要がある場合は、_Laravel Passport_ルートの独自の実装を作成できます。

最初に、_AuthServiceProvider@boot_メソッドからPassport::route();を削除してPassportルートへのアクセスを削除し、必要なルート(たとえば、oauth/token)のみを作成します。

次に、_Laravel\Passport\Http\Controllers\AccessTokenController_によって提供されるいくつかの機能を使用できるようにするために、_Laravel Passport_を拡張する独自のコントローラーを作成します。このコントローラーでは、ルートに必要な数のメソッドを作成できます。
これはtoken routeの例です:

_use Laravel\Passport\Http\Controllers\AccessTokenController;
use Laravel\Passport\TokenRepository;
use Lcobucci\JWT\Parser as JwtParser;
use League\OAuth2\Server\AuthorizationServer;
use Psr\Http\Message\ServerRequestInterface;

class MyOAuthController extends AccessTokenController
{
    public function __construct(AuthorizationServer $server, TokenRepository $tokens, JwtParser $jwt)
    {
        parent::__construct($server, $tokens, $jwt);
    }

    public function token(ServerRequestInterface $request)
    {
        $data = $request->getParsedBody();
        // You can inject your secrets here
        $data['grant_type'] = 'password';
        $data['client_secret'] = 'SECRET-HERE';
        $data['client_id'] = 'ID HERE';
        return parent::issueToken($request->withParsedBody($data));
    }
}
_

必要に応じて、_parent::issueToken_の結果を中間変数に格納して、クライアントに返す代わりに使用することもできます。

次に、ルートを_AuthServiceProvider@boot_メソッドで宣言します。

_Route::post('/myserver/mytokenroute', '\App\Http\Controllers\MyOAuthController@token');
_

ミドルウェアを使用できることを忘れないでください。たとえば、これを使用して単一のクライアントの要求を制限します。

_Route::middleware('throttle:10,1')->post('/myserver/mytokenroute', '\App\Http\Controllers\MyOAuthController@token');
_

このアプローチではプロキシを使用しないため、他のすべてのフロー(認証コード、更新フローなど)をより適切に制御できるため、_Personal Access Token_を使用するよりも優れていると思います。特定の動作が必要な場合は、新しいメソッドを作成してそれをルートにリンクするだけで、ソースコードがクリーンで単一のコントローラーに集中化されます。

3
Damien

以前に行ったことは、_Laravel\Passport\Http\Controllers\AccessTokenController_を拡張する独自のコントローラーを実装しています。トークンの作成を処理するようにこのコントローラーをセットアップできるだけでなく、トークンを更新および取り消すためのルートを追加することもできます。

たとえば、ログインフォームを表示するcreateメソッド、アクセストークンを作成するstoreメソッド、更新トークンを使用して期限切れのアクセスを更新するrefreshメソッドがありました。トークン、および提供されたアクセストークンと関連する更新トークンを取り消すためのrevokeメソッド。

プロジェクトはオープンソースではないため、正確なコードを提供することはできませんが、これはstoreメソッドのみを使用した簡単な例のコントローラーです。

_use Illuminate\Http\Request;
use Psr\Http\Message\ServerRequestInterface;
use Laravel\Passport\Http\Controllers\AccessTokenController;

class MyAccessTokenController extends AccessTokenController
{
    public function store(Request $request, ServerRequestInterface $tokenRequest)
    {
        // update the request to force your grant type, id, secret, and scopes
        $request->request->add([
            'grant_type' => 'password',
            'client_id' => 'your-client-id',
            'client_secret' => 'your-client-secret',
            'scope' => 'your-desired-scopes',
        ]);

        // generate the token
        $token = $this->issueToken($tokenRequest->withParsedBody($request->request->all()));

        // make sure it was successful
        if ($token->status() != 200) {
            // handle error
        }

        // return the token
        return $token;
    }
}
_

refreshメソッドは基本的に同じですが、_grant_type_は_refresh_token_になります。

revokeメソッドは、認証されたユーザーのトークン($token = $request->user()->token())を取得し、それを取り消し($token->revoke())して、関連する_oauth_refresh_tokens_テーブルを手動で調べます。トークン(_access_token_id_ = _$token->id_)およびrevokedフィールドをtrueに更新します。

カスタムコントローラーを使用するためのルートをいくつか作成したら、準備完了です(注意:revokeルートには_auth:api_ミドルウェアが必要です)。

0
patricus