Laravel8 Jetstreamを導入した状態でsocialiteによるSNS認証を両立させる

Laravel8が大きく変わっていたので前回の記事で再入門した。
sailコマンドでコンテナの外からartisanコマンドを叩けて便利。

Laravel5,6あたりでSocialiteパッケージによりSNS認証を簡単に実装することができた.
Laravel8+JetstreamにSocialiteを導入してSNS認証してみた.
Jetstreamをインストールし,Jetstreamのrouteがある状態でSocialiteが機能するようにした.

JetstreamのAuthはlaravel/uiのようにお手軽にrouteを変更できない様子.
今回はそれには触れず, 最低限の修正でJetstreamとSocialiteを両立させてみる.

Jetstream導入

sailコマンド経由でインストールしていく。
composer, artisanだけでなく, npmもsailで実行できる.


# jetstreamをインストールする
$ ./vendor/bin/sail composer require laravel/jetstream

# livewireをインストールする migrationファイルを作成する
$ ./vendor/bin/sail artisan jetstream:install livewire

# 作成したmigrationを実行する
$ ./vendor/bin/sail artisan migrate

# npm install , npm run dev
$ ./vendor/bin/sail npm install
$ ./vendor/bin/sail npm run dev

migrationで作られたテーブル達を確認する.
sailからmysqlを叩くことはできそうだが、さらに-eオプションでSQLを続けられなかった。
sail mysqlでいつものmysql clientに繋がる. sailはあくまでもユーザインターフェースなのでこれで良いか.


$ ./vendor/bin/sail mysql
mysql> show tables;
+------------------------+
| Tables_in_example_app  |
+------------------------+
| failed_jobs            |
| migrations             |
| password_resets        |
| personal_access_tokens |
| sessions               |
| users                  |
+------------------------+

http://localhostを叩くと、認証機能が追加されていることを確認できる。

registerから登録してログインすると認証後URL (./dashboard) にredirectされる.
profileに進むとまぁ普通に使いそうな機能が既にインプリメントされていることがわかる.

routeの確認

Jetstreamをインストールした直後にJetstreamにより作られたrouteを確認してみる.
いやー.. too much過ぎだろう…


$ ./vendor/bin/sail artisan route:list
+--------+----------+----------------------------------+---------------------------------+---------------------------------------------------------------------------------+-----------------------------------------------------------+
| Domain | Method   | URI                              | Name                            | Action                                                                          | Middleware                                                |
+--------+----------+----------------------------------+---------------------------------+---------------------------------------------------------------------------------+-----------------------------------------------------------+
|        | GET|HEAD | /                                |                                 | Closure                                                                         | web                                                       |
|        | GET|HEAD | api/user                         |                                 | Closure                                                                         | api                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate:sanctum                  |
|        | GET|HEAD | dashboard                        | dashboard                       | Closure                                                                         | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate:sanctum                  |
|        |          |                                  |                                 |                                                                                 | Illuminate\Auth\Middleware\EnsureEmailIsVerified          |
|        | GET|HEAD | forgot-password                  | password.request                | Laravel\Fortify\Http\Controllers\PasswordResetLinkController@create             | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        | POST     | forgot-password                  | password.email                  | Laravel\Fortify\Http\Controllers\PasswordResetLinkController@store              | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        | GET|HEAD | livewire/livewire.js             |                                 | Livewire\Controllers\LivewireJavaScriptAssets@source                            |                                                           |
|        | GET|HEAD | livewire/livewire.js.map         |                                 | Livewire\Controllers\LivewireJavaScriptAssets@maps                              |                                                           |
|        | POST     | livewire/message/{name}          | livewire.message                | Livewire\Controllers\HttpConnectionHandler                                      | web                                                       |
|        | GET|HEAD | livewire/preview-file/{filename} | livewire.preview-file           | Livewire\Controllers\FilePreviewHandler@handle                                  | web                                                       |
|        | POST     | livewire/upload-file             | livewire.upload-file            | Livewire\Controllers\FileUploadHandler@handle                                   | web                                                       |
|        |          |                                  |                                 |                                                                                 | Illuminate\Routing\Middleware\ThrottleRequests:60,1       |
|        | GET|HEAD | login                            | login                           | Laravel\Fortify\Http\Controllers\AuthenticatedSessionController@create          | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        | POST     | login                            |                                 | Laravel\Fortify\Http\Controllers\AuthenticatedSessionController@store           | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        |          |                                  |                                 |                                                                                 | Illuminate\Routing\Middleware\ThrottleRequests:login      |
|        | POST     | logout                           | logout                          | Laravel\Fortify\Http\Controllers\AuthenticatedSessionController@destroy         | web                                                       |
|        | GET|HEAD | register                         | register                        | Laravel\Fortify\Http\Controllers\RegisteredUserController@create                | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        | POST     | register                         |                                 | Laravel\Fortify\Http\Controllers\RegisteredUserController@store                 | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        | POST     | reset-password                   | password.update                 | Laravel\Fortify\Http\Controllers\NewPasswordController@store                    | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        | GET|HEAD | reset-password/{token}           | password.reset                  | Laravel\Fortify\Http\Controllers\NewPasswordController@create                   | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        | GET|HEAD | sanctum/csrf-cookie              |                                 | Laravel\Sanctum\Http\Controllers\CsrfCookieController@show                      | web                                                       |
|        | GET|HEAD | two-factor-challenge             | two-factor.login                | Laravel\Fortify\Http\Controllers\TwoFactorAuthenticatedSessionController@create | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        | POST     | two-factor-challenge             |                                 | Laravel\Fortify\Http\Controllers\TwoFactorAuthenticatedSessionController@store  | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\RedirectIfAuthenticated:web           |
|        |          |                                  |                                 |                                                                                 | Illuminate\Routing\Middleware\ThrottleRequests:two-factor |
|        | GET|HEAD | user/confirm-password            | password.confirm                | Laravel\Fortify\Http\Controllers\ConfirmablePasswordController@show             | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        | POST     | user/confirm-password            |                                 | Laravel\Fortify\Http\Controllers\ConfirmablePasswordController@store            | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        | GET|HEAD | user/confirmed-password-status   | password.confirmation           | Laravel\Fortify\Http\Controllers\ConfirmedPasswordStatusController@show         | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        | PUT      | user/password                    | user-password.update            | Laravel\Fortify\Http\Controllers\PasswordController@update                      | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        | GET|HEAD | user/profile                     | profile.show                    | Laravel\Jetstream\Http\Controllers\Livewire\UserProfileController@show          | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        |          |                                  |                                 |                                                                                 | Illuminate\Auth\Middleware\EnsureEmailIsVerified          |
|        | PUT      | user/profile-information         | user-profile-information.update | Laravel\Fortify\Http\Controllers\ProfileInformationController@update            | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        | POST     | user/two-factor-authentication   | two-factor.enable               | Laravel\Fortify\Http\Controllers\TwoFactorAuthenticationController@store        | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        |          |                                  |                                 |                                                                                 | Illuminate\Auth\Middleware\RequirePassword                |
|        | DELETE   | user/two-factor-authentication   | two-factor.disable              | Laravel\Fortify\Http\Controllers\TwoFactorAuthenticationController@destroy      | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        |          |                                  |                                 |                                                                                 | Illuminate\Auth\Middleware\RequirePassword                |
|        | GET|HEAD | user/two-factor-qr-code          | two-factor.qr-code              | Laravel\Fortify\Http\Controllers\TwoFactorQrCodeController@show                 | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        |          |                                  |                                 |                                                                                 | Illuminate\Auth\Middleware\RequirePassword                |
|        | GET|HEAD | user/two-factor-recovery-codes   | two-factor.recovery-codes       | Laravel\Fortify\Http\Controllers\RecoveryCodeController@index                   | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        |          |                                  |                                 |                                                                                 | Illuminate\Auth\Middleware\RequirePassword                |
|        | POST     | user/two-factor-recovery-codes   |                                 | Laravel\Fortify\Http\Controllers\RecoveryCodeController@store                   | web                                                       |
|        |          |                                  |                                 |                                                                                 | App\Http\Middleware\Authenticate                          |
|        |          |                                  |                                 |                                                                                 | Illuminate\Auth\Middleware\RequirePassword                |
+--------+----------+----------------------------------+---------------------------------+---------------------------------------------------------------------------------+-----------------------------------------------------------+

Socialite導入

Laravel5とか6あたりではSocialiteパッケージを導入することでSNS認証を簡単に作れた.
Laravel8+Jetstreamでも同じように作れるのか試してみた.
以下の記事を参考にさせていただきました.
【Laravel】JetstreamでSNS認証(ソーシャルログイン)


# Socialite インストール
./vendor/bin/sail composer require laravel/socialite
# google用provider インストール
./vendor/bin/sail composer require socialiteproviders/google

OAuth idとsecret を取得しておく. (id,secretの発行にはこちらを参考にさせていただきました.)
Callback redirect先のURLとして http://localhost/login/google/callback を登録する.

Socialite実装

.envにOAuth認証id,secret,redirectURLを書く.
.env自体はhostから編集すれば良い.


GOOGLE_KEY="*****-*******.apps.googleusercontent.com"
GOOGLE_SECRET="****-****"
GOOGLE_REDIRECT_URI="http://localhost/login/google/callback"

config/servicesに以下の設定を追加する.


'google' => [
  'client_id' => env('GOOGLE_KEY'),
  'client_secret' => env('GOOGLE_SECRET'),
  'redirect' => env('GOOGLE_REDIRECT_URI'),
],

Routeを追加する. Laravel7までとLaravel8ではRouteの書き方が異なる.
Laravel7までは app/Providers/RouteServiceProvider.php に名前空間が定義されているため,
Routeに書くコントローラの名前空間を書かなくても自動的に解決してくれた.
例えば, LoginController::class と書くと,
自動的にApp/Http/Controllers/LoginController::class と解釈された.
Laravel8では, 名前空間を省略できなくなった.


Route::prefix('login/{provider}')->where(['provider'=> 'google'])->group(function(){
   Route::get('/',[App\Http\Controllers\Auth\LoginController::class, 'redirectToProvider'])->name('sns_login.redirect');
   Route::get('/callback/',[App\Http\Controllers\Auth\LoginController::class, 'handleProviderCallback'])->name('sns_login.callback');
});

Socialite Providerを config/app.php のproviders に追加する


/*
 * Socialite Providerをconfig/app.php の providers に追加する
 */
'providers' => [
 ...
   \SocialiteProviders\Manager\ServiceProvider::class,
 ...
],

app/Providers/EventServiceProvider.php を以下の通り変更する.


<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use SocialiteProviders\Manager\SocialiteWasCalled; //追加

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
        // 追加
        \SocialiteProviders\Manager\SocialiteWasCalled::class => [
           'SocialiteProviders\\Google\\GoogleExtendSocialite@handle',
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

SNS認証によるログインを担うコントローラを自力で作成する.


$ ./vendor/bin/sail artisan make:controller Auth\\LoginController
Controller created successfully.

作成したコントローラの中身は以下の通り.


<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class LoginController extends Controller {
    // メディア側へのリダイレクト
    public function redirectToProvider(Request $request) {
        $provider = $request->provider;
        return Socialite::driver($provider)->redirect();
    }
    // メディア側から返されるユーザー情報
    public function handleProviderCallback(Request $request) {
        $provider = $request->provider;
        $sns_user = Socialite::driver($provider)->user();
        $sns_email = $sns_user->getEmail();
        $sns_name = $sns_user->getName();

        // 登録済ならログイン。未登録ならアカウント登録してログイン
        if(!is_null($sns_email)) {
            $user = User::firstOrCreate(   // Userモデルに、レコードがあれば取得、なければ保存
                [ 'email' => $sns_email ],
                [ 'email' => $sns_email, 'name' => $sns_name, 'password' => Hash::make(Str::random())
            ]);
            auth()->login($user);
            session()->flash('oauth_login', $provider.'でログインしました。');
            return redirect('/');
        }
        return '情報が取得できませんでした。';
    }
}

viewを作成する. ファイル名は app/View/auth/login.blade.php.
Routeで書いた sns_login_redirect ページに遷移するリンクがあるだけ.


<div>
    <a href="{{ route('sns_login.redirect', 'google') }}">Google
</div>

Welcomeページのログインを修正

普通は何らかの画面が既にあってそこにSocialiteを組み込むと思うが, 今回は何もないので,
とりあえずWelcomeページのログインをSocialite用に書き換えてみる.

Jetstreamのrouteを変えようとしたが闇が深そうなので見なかったことにする.
ちょっとJetstreamは出来が良くないのかなー..

デフォルトのWelcomeページのログインは, Jetstreamが生成する /login に合わせて作られてある.
このままだと, Jetstreamが作った認証機構が動く.
例えば以下のように変更するとWelcomeページのログインをSocialiteのものに差し替えることができる.
route(‘login’)をroute(‘sns_login.redirect’,’google’)に変更した.
また, registerは不要なので, registerへの遷移リンクを削除した.


    <body class="antialiased">
        <div class="relative flex items-top justify-center min-h-screen bg-gray-100 dark:bg-gray-900 sm:items-center py-4 sm:pt-0">
            @if (Route::has('sns_login.redirect'))
                <div class="hidden fixed top-0 right-0 px-6 py-4 sm:block">
                    @auth
                        <a href="{{ url('/dashboard') }}" class="text-sm text-gray-700 underline">Dashboard
                    @else
                        <a href="{{ route('sns_login.redirect','google') }}" class="text-sm text-gray-700 underline">Log in
                    @endauth
                </div>
            @endif
      ...

動作確認

未ログインの状態で http://localhost を開くと, Welcome画面が表示され, Login への遷移リンクが表示される.
Loginを押下すると, Googleのログイン画面に遷移する.
アカウントを選択すると, http://localhost/login/google/callback にredirectがかかる.
もし当サイトにアカウントがなければ,アカウントを作成する.
アカウントがあれば,そのユーザでログインする.
晴れて, Googleアカウントと同じメールアドレスを持つユーザでログインした状態でダッシュボード(./dashbaord)が開く.