Firebase Authenticationでメール・ソーシャルログイン認証機能(SPA認証)を実装 (Nuxt.js + Laravel) (後編)

## 概要
この記事は以下の記事の続きです。まだご覧になっていない方はぜひ読んでみてください。
前編でNuxtとLaravelのプロジェクトの作成と、Firebaseの導入と設定をしました。この記事では、入力用のフォームの作成、データベースにユーザー情報の登録、ログインの処理を実装します。

https://qiita.com/Kamichan_R/items/6b8fae13997cb464f21b

## 仕様
### フレームワーク等
+ フロントエンド：Nuxt 2, Vuetify
+ バックエンド：Laravel 8, Laravel Sanctum, Firebase Authentication v9
+ その他：dockerとWSL2を使用して開発をします。

### 認証の流れ
認証は以下の図のようになる。初めに、Firebase Authenticationから一意のユーザーUIDを取得する。これがユーザーを識別するキーとなる。次に、Laravel SanctumにユーザーUIDを送信してユーザーを識別しログインする。新規登録の場合はデータベースにユーザーを登録した後にログインする。この記事では、主にNuxtとLaravel間の実装を行う。
![認証の流れ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1663632/a85b4796-4aff-82e1-9930-280e2e1e9bf3.png)

### 作業手順
1. nuxt/authモジュールの導入 (Nuxt側)
2. 新規登録・ログインフォームの作成（Nuxt側）
3. NuxtとLaravelの接続 (Nuxt・Laravel側)
4. ルーティングとコントローラーの設定(Laravel側)

## 1. nuxt/authモジュールの導入 (Nuxt側)
Laravel Sanctumを使用するためにnuxt/authモジュールを導入する必要があるので、以下のコードを実行します。

```
yarn add --exact @nuxtjs/auth-next
yarn add @nuxtjs/axios
```

その後、nuxt.config.jsのmodule欄に以下のコードを追加します。

```javascript:client/nuxt.config.js
modules: [
  // 省略
  '@nuxtjs/auth-next',
]
```

## 2. 新規登録・ログインフォームの作成（Nuxt側）
この記事では、バリデーションやパスワード確認の機能は無視して実装を行う。新規登録フォームでは、ユーザー名入力欄・メールアドレス入力欄・パスワード入力欄のボタンを配置する。ログインフォームでは、メールアドレス入力欄・パスワード入力欄・ソーシャルログイン用のボタンを配置する。注意事項として、新規登録フォームはメールアドレスでの新規登録のみに用いて、ソーシャルログインはログインフォームから行うものとする。
初めに新規登録フォームを作成します。pagesディレクトリにregister.vueを作成して以下のコードを入力します。

```vue:client/pages/register.vue
<template>
  <v-row>
    <v-col>
      <v-card>
        <v-card-title>新規登録フォーム</v-card-title>
        <v-form class="pa-4">
          <v-text-field v-model="form.name" label="ユーザー名" />
          <v-text-field v-model="form.email" label="メールアドレス" />
          <v-text-field v-model="form.password" label="パスワード" />
          <v-btn @click="register">新規登録</v-btn>
        </v-form>
      </v-card>
    </v-col>
  </v-row>
</template>

<script>
import { getAuth, createUserWithEmailAndPassword } from '@firebase/auth';

export default {
  name: 'RegisterPage',
  data() {
    return {
      form: {
        name: '',
        email: '',
        password: '',
      },
    };
  },
  methods: {
    async register() {
      // Firebaseにユーザーを登録して、その認証情報(ユーザーUIDなど)を取得する。
      const auth = getAuth();
      const userCredential = await createUserWithEmailAndPassword(auth, this.form.email, this.form.password);
      
      // Laravelに送信する用のuserオブジェクトを作成する。
      const user = {
        name: this.form.name,
        uid: userCredential.user.uid,
      };

      // CSRF保護の初期化とXSRF-TOKENクッキーの取得
      await this.$axios.get('sanctum/csrf-cookie');

      // Laravelにユーザー情報を送信する。
      this.$auth.loginWith('laravelSanctum', {
        withCredential: true,
        data: user,
      });
    },
  },
}
</script>
```

次に、ログインフォームを作成します。pagesディレクトリにlogin.vueを作成して以下のコードを入力します。（この記事では、GoogleとTwitterのソーシャルログインを実装します。）

```vue:pages/login.vue
<template>
  <v-row>
    <v-col>
      <v-card>
        <v-card-title>ログインフォーム</v-card-title>
        <v-form class="pa-4">
          <v-text-field v-model="form.email" label="メールアドレス" />
          <v-text-field v-model="form.password" label="パスワード" />
          <v-btn @click="login">ログイン</v-btn>
        </v-form>
      </v-card>
    </v-col>
  </v-row>
</template>

<script>
import { getAuth, signInWithEmailAndPassword, signInWithPopup, GoogleAuthProvider, TwitterAuthProvider } from '@firebase/auth';

export default {
  name: 'LoginPage',
  data() {
    return {
      form: {
        email: '',
        password: '',
      },
    };
  },
  methods: {
    // メールアドレスでログイン
    async login() {
      const auth = getAuth();
      const userCredential = await signInWithEmailAndPassword(auth, this.form.email, this.form.password);
      const user = {
        uid: userCredential.user.uid,
      };

      await this.$axios.get('sanctum/csrf-cookie');

      this.$auth.loginWith('laravelSanctum', {
        withCredential: true,
        data: user,
      });
    },
    // Googleアカウントでログイン
    async loginWithGoogle() {
      const auth = getAuth();
      const provider = new GoogleAuthProvider();
      const userCredential = await signInWithPopup(auth, this.form.email, this.form.password);
      const user = {
        uid: userCredential.user.uid,
      };

      await this.$axios.get('sanctum/csrf-cookie');

      this.$auth.loginWith('laravelSanctum', {
        withCredential: true,
        data: user,
      });
    },
    // Twitterアカウントでログイン
    async loginWithTwitter() {
      const auth = getAuth();
      const provider = new TwitterAuthProvider();
      const userCredential = await signInWithPopup(auth, this.form.email, this.form.password);
      const user = {
        uid: userCredential.user.uid,
      };

      await this.$axios.get('sanctum/csrf-cookie');

      this.$auth.loginWith('laravelSanctum', {
        withCredential: true,
        data: user,
      });
    },
  },
}
</script>
```

## 3. NuxtとLaravelの接続 (Nuxt・Laravel側)
NuxtとLaravelを接続するためにaxiosの設定をします。この記事ではLaravel側のポートを8000番とします。以下のコードを入力します。

```javascript:client/nuxt.config.js
axios: {
  baseURL: 'http://localhost:8000',
  credentials: true,
  proxy: true,
},

proxy: {
  '/api': 'http://localhost:8000',
  '/sanctum': 'http://localhost:8000',
},

auth: {
  strategies: {
    laravelSanctum: {
      provider: 'laravel/sanctum',
      url: 'http://localhost:8000',
      endpoints: {
        login: {
          url: '/api/auth/login',
        },
        logout: {
          url: '/api/auth/logout',
        },
        user: {
          url: '/api/user',
        },
      },
    },
  },
  redirect: {
    login: 'auth/login',
    logout: '/',
    home: '/',
  },
},
```

```:server/.env
APP_PORT=8000
```

## 4. ルーティングとコントローラーの設定(Laravel側)
Nuxtからユーザー情報を取得し、その処理を行うための機能を実装します。初めにユーザーの新規登録とログインのためにLoginControllerを作成します。以下のコードを実行・入力します。

```
sail artisan make:controller Auth/LoginController
```

```php:server/app/Http/Controllers/Auth/LoginController.php
// ログインの処理
public function login(Request $request)
{
    // 新規登録の場合はユーザーUIDを設定し、登録済みの場合は該当するUserモデルを取得する。
    $user = User::firstOrNew(['uid' => $request->uid]);

    // 新規登録の場合はユーザーを作成してデータベースに保存する。
    if (!$user->exists) $user = $this->createUser($user, $request);

    // ログイン
    Auth::login($user);

    // Nuxtにレスポンスを返す、
    return response(null, Response::HTTP_OK);
}

// ユーザーの作成とデータベースに保存
public function createUser(User $user, Request $request)
{
    $user->name = $request->name;
    $user->save();

    return User::where('uid', $user->uid)->first();
}
```

次に、Nuxtからユーザー情報を受け取り、認証結果を送信するためのルーティングを定義します。以下のコードを入力します。

```php:server/routes/api.php
Route::post('auth/login', [LoginController::class, 'login'])->middleware('guest');
```

最後に、Userモデルの修正をします。

```php:server/app/Models/User.php
protected $fillable = ['name', 'uid'];
```

これによって、Nuxtから受け取ったユーザー名とユーザーUIDをデータベースに保存し、ログインをすることができます。

## 完成
以上でFirebase AuthenticationとNuxt.js, Laravelを用いた認証機能が実装できました。私の環境ではこの方法で正しく動作しましたが、各々の環境で細かい部分などで正常に動作しない場合もあるかもしれないので、ご了承ください。

## 関連記事

https://qiita.com/Kamichan_R/items/6b8fae13997cb464f21b