Laravel+Nuxt.jsでGoogleログイン認証

# はじめに
LaravelとNuxt.jsのリポジトリを完全に分離してSNSログイン認証を実装してみた。
SocialiteはLaravelリポジトリ内のbladeを使う前提で設計されており、紹介されている記事もこの方法ばかりで、結構苦労した。
Firebaseのログイン認証やAuth0を利用する方法もあるようだが、今回はSocialiteを使用する。

SocialiteをそのままAPIとして流用すると、セッションエラーや、CORSエラーが出る。リダイレクトによるCORSエラーは解決方法がないようなので、リダイレクト先をフロントエンドにし、フロントエンドからクエリをAPIに送るようにする。

主にこちらの記事を参考にしてます。ほぼ同じです。ありがとうございます。

https://qiita.com/hareku/items/ea09602bf40bf0a42040

使用環境
- laravel v9.5.1
- Nuxt.js v2.15.7

# Google Cloud Platform の登録

登録方法はこちらを参照する。

https://qiita.com/u-dai/items/91df3b923dc82fed5b76

注意点は、「承認済みのリダイレクト URI」には、フロントエンドのコールバック先を入力する。
ファイルは後ほど作成するが、 ```http://localhost:3000/login/googleCallback``` にしておく。

忘れないうちに、laravelの.envファイルにクライアントIDとクライアントシークレットを保存する。CALLBACKは、「承認済みのリダイレクト URI」に登録したものと同じにする。
```.env
GOOGLE_CLIENT_ID=******
GOOGLE_CLIENT_SECRET=******
GOOGLE_CALLBACK=http://localhost:3000/login/googleCallback
```

# Laravel側の設定

## users_tableの編集

まず、デフォルトのusersテーブルはpasswordが必須になっている。Googleログインではデータベースにパスワードを保存しないため、nullableの設定にしておく。
アバター画像、プロバイダー、プロバイダー先のIDも保存したいので追加する。

```database/migrations/2014_10_12_000000_create_users_table.php
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password')->nullable();
            $table->string('avatar')->nullable();
            $table->string('provider')->nullable();
            $table->string('provider_id')->nullable()->unique();
            $table->rememberToken();
            $table->timestamps();
        });
    }
```

migrateを実行する。
```
php artisan migrate
```

モデルのfillableに先程のカラムを追加する。
```app/Models/User.php
    protected $fillable = [
        'name',
        'email',
        'password',
        'avatar',
        'provider',
        'provider_id'
    ];
```

## Socialiteの設定

インストール
```
composer require laravel/socialite
```

app.phpにproviderとaliaseを追加する。
```config/app.php
    'providers' => [
        // 中略
        Laravel\Socialite\SocialiteServiceProvider::class, // 追加
    ],

    'aliases' => Facade::defaultAliases()->merge([
        'Socialite' => Laravel\Socialite\Facades\Socialite::class, // 追加
    ])->toArray(),
```

services.phpに追加。
```config/services.php
    'google' => [
        'client_id'     => env('GOOGLE_CLIENT_ID'),
        'client_secret' => env('GOOGLE_CLIENT_SECRET'),
        'redirect'      => env('GOOGLE_CALLBACK'),
    ],
```

新しくコントローラーを作成する。
```
php artisan make：controller SocialController
```

```app/Http/Controllers/SocialController.php
<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Socialite;
use Auth;
Use App\Models\User;

class SocialController extends Controller
{
    public function redirect($provider)
    {
     return Socialite::driver($provider)->redirect()->getTargetUrl();
    }

    public function Callback($provider){
        $userSocial  =   Socialite::driver($provider)->stateless()->user();
        $userAcount  =   User::where(['email' => $userSocial->getEmail()])->first();

        if($userAcount){
            $user = User::find($userAcount->getAttribute('id'));
            Auth::login($userAcount);
        }else{
            $user = User::create([
                'name'          => $userSocial->getName(),
                'email'         => $userSocial->getEmail(),
                'avatar'        => $userSocial->getAvatar(),
                'provider_id'   => $userSocial->getId(),
                'provider'      => $provider,
            ]);
        }

        return [
            'user'  => $user,
            'access_token' => $user->createToken('user')->plainTextToken,
        ];
    }
}
```
API側でリダイレクトさせると、CORS設定をしていてもエラーが出る。リダイレクトして認証サーバーを経由したときはCORSエラーが出てしまう。これの解決方法はないらしい。

https://qiita.com/att55/items/2154a8aad8bf1409db2b#%E3%81%BE%E3%81%A8%E3%82%81

そのため、一旦フロントでURLを受け取り、フロントのドメインからコールバック処理を行うようにする。
redirect関数では```redirect()->getTargetUrl()``` でURLのみをレスポンスに返す。

Callback関数では、ユーザー登録を行い、レスポンスでaccess_tokenを返す。

## ルートの設定
普通にapi.phpに追加すると、```Session store not set on request.``` のエラーが出る。
セッション許可を与えるためmiddlewareGroupsを編集。apiグループに追加すると、他のAPIにも影響してしまうため、sessionグループを新たに追加する。

```app/Http/Kernel.php
    protected $middlewareGroups = [
        // 中略
        'session' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
        ],
    ];

```

```routes/api.php
use App\Http\Controllers\Auth\SocialController;

Route::group(['middleware' => ['session']], function () {
    Route::get('login/{provider}', [SocialController::class,'redirect']);
    Route::get('login/{provider}/callback',[SocialController::class,'callback']);
});
```

## CORSの設定

```config/cors.php
'allowed_origins' => explode(',', env('ALLOWED_ORIGINS', '')),
```

```.env
ALLOWED_ORIGINS=http://localhost:3000
```

# Nuxt.jsの設定
axiosは入っている前提で省略。

## CORSの設定
```nuxt.config.js
  publicRuntimeConfig: {
    axios: {
      baseURL: process.env.API_URL,
      credentials: true // 追加
    },
  },
```

## Googleログインボタンの実装

```pages/login/googleLogin.vue
<template>
  <v-btn @click="login">Google login</v-btn>
</template>

<script>
  export default {
    methods: {
       async login(provider) {
        try {
          const response = await this.$axios.$get('login/' + provider)
          window.location.href = response
        } catch (err) {
          console.log(err)
        }
      },
    },
  }
</script>
```

ボタンを押すと、```api/login/{provider}```を呼び、リダイレクト先のURLをレスポンスから取得する。
URLは```https://accounts.google.com/o/oauth2/auth/oauthchooseaccount?client_id=******&redirect_uri=******```の形式。
レスポンスを取得できたら、そのままリダイレクトし、Googleログイン画面を表示させる。
アカウントを選択してログインが成功すると、「承認済みのリダイレクト URI」として登録した```http://localhost:3000/login/googleCallback```に移動する。

## callback先のページ実装
```pages/login/googleCallback.vue
<template>
  <div>
    認証中
  </div>
</template>

<script>
  export default {
    async mounted () {
      try {
        const response = await this.$axios.$get('login/google/callback', { params: this.$route.query })
        this.$store.commit('setToken', {token: response.access_token})
        this.$store.commit('setUser', {user: response.user})
        this.$router.replace('/')
      } catch (error) {
        console.log(error)
      }
    }
  }
</script>
```
Googleログイン画面から戻ると、URLにパラメータが追加されているので、それをそのままAPIに渡す。
```api/login/{provider}/callback```を呼び、ユーザー登録処理を行う。

これでusersテーブルにデータが登録される。
ログイン後の処理は、VuexにAccessTokenを保存する。Vuexについてはもとの参考記事が分かりやすくておすすめです。

# 参考記事
https://qiita.com/hareku/items/ea09602bf40bf0a42040

https://medium.com/@Alabuja/social-login-in-laravel-with-socialite-90dbf14ee0ab

https://qiita.com/u-dai/items/91df3b923dc82fed5b76

https://stackoverflow.com/questions/34449770/laravel-session-store-not-set-on-request