Laravel Passport で認可サーバを作り、認可サーバで認証するまでのサンプルを作ってみてのメモ

概要

クライアントアプリ (Laravel) から Laravel Passport でログイン

基本的に sample_laravel5_8_oauth2/README.md at master · oki2a24/sample_laravel5_8_oauth2 のコピペです。

具体的な実装

  • client/app/Http/Controllers/SampleController.php ログインするための処理は全てここに集約しました。
  • client/config/services.php
    • Laravel Passport でログインするための、 client id 等の設定をまとめました。
  • client/resources/views/sample/index.blade.php
    • Laravel Passport でログインするボタンを設置しました。
  • client/routes/web.php
    • ログインするために必要なルートを定義しました。

設定は、 .env に書きますが、たとえば次のようになりました。 注) LARAVELPASSPORT_AUTH_URI と LARAVELPASSPORT_TOKEN_URI の FQDN は Laravel Passport サーバとします。したがって本来ならば同じとなります。今回は、 Docker を使ってサンプルを構築した関係で、リダイレクトするときは Docker ホストから見た FQDN を、Guzzle でリクエストするときは Docker コンテナから見た FQDN となったため、異なっています。

LARAVELPASSPORT_KEY=2
LARAVELPASSPORT_SECRET=eI7yOgFrNn9CiMRmL7i7inSMyjDoluGetbOLPXfn
LARAVELPASSPORT_REDIRECT_URI=https://localhost:4433/sample/callback
LARAVELPASSPORT_AUTH_URI=https://localhost/oauth/authorize
LARAVELPASSPORT_TOKEN_URI=https://nginx/oauth/token

作ったものを動かしてみてわかったこと

"クライアントアプリで、Laravel Passport でログイン" -> ログイン画面 -> Authorize -> クライアントアプリ Laravel Passport 側で、 Remember Me にチェックを打っておくと、次の "Laravel Passport でログイン" の認証処理をすっ飛ばせる。

クライアントアプリへ認証からコールバックで戻ってきて、次は Laravel Passport へ POST リクエストし許可コードからアクセストークンへの変換を行うという時、リクエストまでの時間が長すぎると次のエラーになった。

[2019-09-14 23:34:11] local.ERROR: Client error: `POST https://nginx/oauth/token` resulted in a `400 Bad Request` response:
{"error":"invalid_request","error_description":"The request is missing a required parameter, includes an invalid paramet (truncated...)
 {"exception":"[object] (GuzzleHttp\\Exception\\ClientException(code: 400): Client error: `POST https://nginx/oauth/token` resulted in a `400 Bad Request` response:
 {\"error\":\"invalid_request\",\"error_description\":\"The request is missing a required parameter, includes an invalid paramet (truncated...)
  at /var/www/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php:113)
 [stacktrace]
#0 /var/www/vendor/guzzlehttp/guzzle/src/Middleware.php(66): GuzzleHttp\\Exception\\RequestException::create(Object(GuzzleHttp\\Psr7\\Request), Object(GuzzleHttp\\Psr7\\Response))

足りていないところ


クライアントアプリ (Laravel Socialite) から Laravel Passport でログイン

基本的に sample_laravel5_8_oauth2/README.md at master · oki2a24/sample_laravel5_8_oauth2 のコピペです。

実装

  • app/Http/Controllers/Auth/LoginController.php ログインするための処理はここに集約しました。
  • config/services.php Laravel Socialite を使うための設定を書きました。
  • routes/web.php ログインするためのルートを集約しました。

設定は .env に書きますが、例えば次のようになりました。

LARAVELPASSPORT_KEY=4
LARAVELPASSPORT_SECRET=z7oC3Qe2nzXwEN2jD76nAqhwfZMUzloBA5AijAUy
LARAVELPASSPORT_REDIRECT_URI=https://localhost:4434/login/laravelpassport/callback

動かすために学んだこと

リダイレクト先の URL の FQDN を設定したい

何も設定しない場合、認証するためのリダイレクト URL は次となった。

https://localhost:4434/oauth/authorize?client_id=2&redirect_uri=https%3A%2F%2Flocalhost%3A4434%2Fpassport%2Fcallback&scope=&response_type=code&state=nICP1wSK6mpWBHyKqlNOsGNZ0DBd4CueErfIiuMZ

リダイレクト先の URL がクライアントサイトとなっているので、これを Laravel Paspport のサイトにしたい。 https://github.com/SocialiteProviders/Providers/blob/master/src/LaravelPassport/Provider.php#L125 の host の値がそれに当たる。 https://github.com/SocialiteProviders/Providers/blob/master/src/LaravelPassport/Provider.php#L32 に設定できればよいがどうすればよいか? どうやら、 https://github.com/SocialiteProviders/Providers/blob/master/src/LaravelPassport/Provider.php#L29 の additionalConfigKeys の値を設定するには、 config/services.php で設定すればよいだけだった。

    'laravelpassport' => [
        'client_id' => env('LARAVELPASSPORT_KEY'),
        'client_secret' => env('LARAVELPASSPORT_SECRET'),
        'redirect' => env('LARAVELPASSPORT_REDIRECT_URI'),
        'host' => 'https://localhost',
    ],

これを突き止めるために、 config の値をみたかったので、パッケージを強引に修正してみられるようにした。これはもちろん一時的なそち。 https://github.com/SocialiteProviders/Manager/blob/e3e8e78b9a3060801cd008941a0894a0a0c479e1/src/ConfigTrait.php#L44 具体的にはこのメソッドのスコープを public にした。

凡ミス。クライアントアプリが異なる、コールバック URL が異なるなら、新たに Laravel Passport クライアントを作成すること

https://localhost/oauth/authorize?client_id=2&redirect_uri=https%3A%2F%2Flocalhost%3A4434%2Fpassport%2Fcallback&response_type=code&scope=&state=nvaGAyXkED4mO1tqvqYXVPlODSxsxwaTnk6G4ynE {"error":"invalid_client","error_description":"Client authentication failed","message":"Client authentication failed"} クライアントアプリが異なるので、別の Laravel Passport クライアントを発行する必要があった。

Client ID: 4 Client secret: z7oC3Qe2nzXwEN2jD76nAqhwfZMUzloBA5AijAUy

また他に気が付いたこととして、 url に state があった。もしかして、 Laravel Passport へリダイレクトするときに、 state をつけると、戻ってくるときに state をそのままの値で返してくれるのかもしれない。要検証。 <- 多分そう。

Laravel\Socialite\Two\InvalidStateException になる

リダイレクト前に、セッションにキーが state 、 値が ランダム値を保存している。 コールバックに戻ってきたとき、 state の値が消えているため、コールバック時の URL のパラメータについていた state と一致させることができず、発生している。

SocialiteにおけるInvalidStateExceptionって – Qiita

ただし、セッション管理がファイルでは不可能で、 DB である必要があるようだ。 もう一つの解決方法は、 stateless にすること。こちらでまずやってみる。セキュリティーが甘くなってしまう。広く公開するクライアントアプリの場合は不可だが、内部的な場合なので見逃しておく。

Laravel Passport の URL が変化する

GuzzleHttp\Exception\ConnectException cURL error 7: Failed to connect to localhost port 443: Connection refused (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) 単純に接続できていない。 redirect メソッドで指定するときの Laravel Passport の URL は Docker ホストから見たものなので localhost で、 Socialite 内部から curl するときの Laravel Passport の URL は Docker コンテナから見たものなので nginx となる。 よって、設定し直す。

自己署名の証明証でも無理やり処理する

GuzzleHttp\Exception\RequestException cURL error 60: SSL certificate problem: self signed certificate (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) "vendor/laravel/socialite/src/Two/AbstractProvider.php" https://github.com/laravel/socialite/blob/4.0/src/Two/AbstractProvider.php#L263-L266 を次のようにした。公式ドキュメントにもあるが、これは本当はやっちゃダメ。

どのみち、依存パッケージの中身を書き換えているので、本番では使えない。

        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
            'headers' => ['Accept' => 'application/json'],
            $postKey => $this->getTokenFields($code),
            'verify' => false,
        ]);

GuzzleHttp\Exception\RequestException cURL error 60: SSL certificate problem: self signed certificate (see http://curl.haxx.se/libcurl/c/libcurl-errors.html) 同様のエラー https://github.com/SocialiteProviders/Providers/blob/master/src/LaravelPassport/Provider.php#L76-L79

        $response = $this->getHttpClient()->get($this->getLaravelPassportUrl('userinfo_uri'), [
            'headers' => [
                'Authorization' => 'Bearer '.$token,
            ],
            'verify' => false,
        ]);

後日、 HTTPS ではなく、 HTTP ならばこの問題は発生しないことに気がつき、こちらの通信を使うようにして、この問題を回避した。

デバッグで役に立ったログ出力

        logger('session all');
        logger($request->session()->all());
        logger('request state');
        logger($request->input('state'));

おわりに

サンプルを作っていく中でつまづいたことや気がついたことをとりとめもなくメモしていったものが本投稿となります。

まとまっておらず、読みやすいとは言えませんけれども、もともとメモですので気にせずに残しておきます。

以上です。

コメントを残す

コメントを残す