【中編】Next.jsでログインフォームを実装する 〜firebase authentication編〜

# 概要
前編に引き続き、Next.jsのログインフォームについて中編です！
今回は、firebaseの初期設定をして、authに新規登録して、ログインすることとcloud firestoreにユーザ情報を格納するまでを実装していこうと思います！
**前回の記事**
前編 chakra-ui×react-hook-form編

https://zenn.dev/ko_hei/articles/28e7709e187c0a

**中編 firebase authentication編** ←今回の記事です
後編 バリデーションとトリガー編

使用する技術は以下になります。

- Next.js
- chakra-ui
- react-hook-form
- firebase
  - authentication
  - cloud firestore
  - cloud functions

# firebaseの初期設定
前回までで、サインイン画面、サインアップ画面の画面の実装までを終えています。
本記事ではfirebaseとの接続を設定していきます。この章では、firebaseの新規プロジェクト作成と、初期設定について説明していきます。
まずは、firebaseのコンソールから、新規プロジェクトを作成していきます。

https://firebase.google.com/?hl=ja

### 1. プロジェクトの作成
プロジェクト名を`auth-form`として作成します（名前は任意で構いません）
「続行」ボタンを押下して、次の画面に遷移します。
次の画面以降は、初期状態で「続行」、自身のアカウントを選択して、プロジェクトを新規作成します。
![](https://storage.googleapis.com/zenn-user-upload/73c88d84adce-20230903.png)


### 2. webアプリの登録
プロジェクトが作成されたら、次のような画面が表示されます。
次に、firebaseを使用するwebアプリの登録を行います。
画像の赤枠で囲まれているアイコンを押下してwebアプリを登録していきます。
![](https://storage.googleapis.com/zenn-user-upload/e403c387d08b-20230903.png)
アプリ名を入力して「アプリを登録」ボタンを押下します。
※Hostingについて今回は、チェックをつけていませんが、今後利用する予定があれば、チェックをつけてください
![](https://storage.googleapis.com/zenn-user-upload/834266956962-20230903.png)
登録すると、Firebase SDKが表示され、`npm install`コマンドと接続に必要な`key`が表示されると思います。
この`key`はNext.jsのプロジェクトとfirebaseの接続で使用します。他人に知られてしまうと悪用されたり、第三者がfirebaseに接続できてしまうので、扱いには注意が必要です！

### 3. Authenticationの有効化
それでは次に、firebase authenticationを有効化し、今回、Email／Password認証を使って実装していくので、認証プロバイダーを有効化していきましょう。
authenticationはサイドバーの「構築」タブにあるので、押下します。
![](https://storage.googleapis.com/zenn-user-upload/7a58a1b10f5c-20230903.png)
「始める」ボタンでauthenticationを利用することができます。
![](https://storage.googleapis.com/zenn-user-upload/a728f106e4a4-20230903.png)
「メール/パスワード」を選択して、有効化します。
※パスワードなしでログインもありますが、これは無効の状態にしておいてください。
![](https://storage.googleapis.com/zenn-user-upload/8ba8ad0a24ff-20230903.png)

![](https://storage.googleapis.com/zenn-user-upload/c53b6f65326c-20230903.png)

これで、firebase側のauthenticationの設定は完了です！

# Next.jsとfirebaseの接続
次は、設定したfirebaseのプロジェクトをNext.jsでも使用できるように設定をしていきます。
まずは、firebaseで生成した`key`をNext.jsで使用できるように環境変数として値を格納しておきます。

### 1. パッケージのインストール
firebaseのメソッドや型をNext.jsで使用するために、下記のコマンドを実行して、パッケージをインストールします。
```
npm install firebase
```

### 2. 環境変数の設定
Next.jsプロジェクトの最上層に`.env`ファイルを作成します。
```:.env
# firebase
NEXT_PUBLIC_FIREBASE_API_KEY='自身のfirebaseプロジェクトのapiKey'
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN='自身のfirebaseプロジェクトのauthDomain'
NEXT_PUBLIC_FIREBASE_PROJECT_ID='自身のfirebaseプロジェクトのprojectId'
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET='自身のfirebaseプロジェクトのstorageBucket'
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID='自身のfirebaseプロジェクトのmessagingSenderId'
NEXT_PUBLIC_FIREBASE_APP_ID='自身のfirebaseプロジェクトのappId'
```

### 2. firebase config
環境変数の準備ができたので、firebaseの初期化をしていきます！下記のフォルダ構成で実装します。
```
src
 ├ app
 ├ common
 └ lib
     └ firebase
         ├ apis
         │  ├ auth.ts 
         │  └ user.ts
         └ config.ts
```

firebaseの初期化は`config.ts`で設定します
```typescript: config.ts
import { initializeApp } from 'firebase/app'
import { getAuth } from 'firebase/auth'
import { getFirestore } from 'firebase/firestore'

export const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}

const app = initializeApp(firebaseConfig)
export const auth = getAuth(app)
export const db = getFirestore(app)
```

これで、firebaseとNext.jsの接続の手順が完了しました。
正常にauthに登録されるかどうか確かめてみましょう。

# フォームとfirebaseの接続
実際に、authにユーザを登録する処理を実装していきます。
```
src
 ├ app
 ├ common
 └ lib
     └ firebase
         ├ apis
         │  ├ auth.ts //←ここに実装
         │  └ user.ts
         └ config.ts
```
Email／Passwordの認証メソッドをそれぞれ宣言します。（ログアウト処理も一応宣言しておきます。）
```typescript
import {
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  signOut,
} from 'firebase/auth'

import { auth } from '@/lib/firebase/config'

/**
 * EmailとPasswordでサインイン
 * @param email
 * @param password
 * @returns Promise<boolean>
 */
export const signInWithEmail = async (args: {
  email: string
  password: string
}): Promise<boolean> => {
  let result: boolean = false
  try {
    const user = await signInWithEmailAndPassword(
      auth,
      args.email,
      args.password
    )

    if (user) {
      result = true
    }
  } catch (error) {
    result = false
    console.log(error)
  }
  return result
}

/**
 * EmailとPasswordでサインアップ
 * @param username
 * @param email
 * @param password
 * @returns Promise<boolean>
 */
export const signUpWithEmail = async (args: {
  email: string
  password: string
}): Promise<boolean> => {
  let result: boolean = false
  try {
    const user = await createUserWithEmailAndPassword(
      auth,
      args.email,
      args.password
    )
    if (user) {
      result = true
    }
  } catch (error) {
    result = false
    console.log(error)
  }
  return result
}

/**
 * ログアウト処理
 * @returns Promise<boolean>
 */
export const logout = async (): Promise<boolean> => {
  let result: boolean = false

  await signOut(auth)
    .then(() => {
      result = true
    })
    .catch((error) => {
      console.log(error)
      result = false
    })

  return result
}
```

auth処理が実装できたら、ログインフォームで入力された値を使って、auth処理を呼び出していきたいと思います。
```typescript: signin/page.tsx
..省略..
/** サインイン画面
 * @screenname SignInScreen
 * @description ユーザのサインインを行う画面
 */
export default function SignInScreen() {
  const { handleSubmit, register } = useForm<formInputs>()

  const [show, setShow] = useState<boolean>(false)

  const onSubmit = handleSubmit(async (data) => {
    /** ここから処理追加 */
    signInWithEmail({ email: data.email, password: data.password }).then(
      (res: boolean) => {
        if (res) {
          console.log('ログイン成功')
        } else {
          console.log('ログイン失敗')
        }
      }
    )
  })
  return (..省略..)
}
```
ログインが成功したら、コンソールに「ログイン成功」、失敗したら、「ログイン失敗」の文言が表示されます。
続いて、サインアップ画面に処理を追加していきます。
```typescript: signup/page.tsx
..省略..
/** サインアップ画面
 * @screenname SignUpScreen
 * @description ユーザの新規登録を行う画面
 */
export default function SignUpScreen() {
  const { handleSubmit, register } = useForm<formInputs>()

  const [password, setPassword] = useState(false)
  const [confirm, setConfirm] = useState(false)

  const onSubmit = handleSubmit(async (data) => {
    /** ここから処理追加 */
    signUpWithEmail({ email: data.email, password: data.password }).then(
      (res: boolean) => {
        if (res) {
          console.log('新規登録成功')
        } else {
          console.log('新規登録失敗')
        }
      }
    )
  })

  const passwordClick = () => setPassword(!password)
  const confirmClick = () => setConfirm(!confirm)

  return (..省略..)
}

```
サインアップでも、同様に`onSubmit`メソッドで新規登録処理を呼び出します。
これで、フォームとfirebaseのauth処理との接続ができました！
実際に画面を操作して、ユーザが新規登録されるか、そして、作成したユーザでログインできるかを確かめてみましょう！

:::note warn
firebaseをnpm installすると、`Module not found: Can't resolve 'encoding' ...`というエラーがコンソールに表示されます。されない方はそのままで問題ありませんが、表示されている方は`npm i -D encoding`をコマンドで実行してみてください！
:::

下記の入力情報で、新規作成を実施してみます。
![](https://storage.googleapis.com/zenn-user-upload/41dc4372d5fe-20230903.png)
コンソールに「新規登録成功」と表示されています！※2回押しちゃってんで、2回目はエラーが出てます。
![](https://storage.googleapis.com/zenn-user-upload/9eff7ec9c4c1-20230903.png)
firebase consoleを確認してみます！ユーザが追加されていることがわかります！
![](https://storage.googleapis.com/zenn-user-upload/19fb8b1a2f9c-20230903.png)

これでfirebase authenticationにユーザ登録完了です！
では、追加された後にユーザ情報をcloud firestoreに格納する処理を追加しましょう！
まずは、firestoreを有効化します！「データベース作成」から、本番環境、地域を東京で選択してfirestoreを使用できるように設定します
![](https://storage.googleapis.com/zenn-user-upload/334dba9485ad-20230903.png)

firestoreは初期設定で、セキュリティルールの設定で、権限がない状態にあるので、まずは、フル権限で設定しておきます。
![](https://storage.googleapis.com/zenn-user-upload/f8ec2042c6af-20230903.png)

ここまで設定できたら、サインアップ処理にコードを追加します！
```typescript: apis/auth.tsx
/**
 * EmailとPasswordでサインアップ
 * @param username
 * @param email
 * @param password
 * @returns Promise<boolean>
 */
export const signUpWithEmail = async (args: {
  email: string
  password: string
}): Promise<boolean> => {
  let result: boolean = false
  try {
    const user = await createUserWithEmailAndPassword(
      auth,
      args.email,
      args.password
    ).then(async (userCredential) => {
      /** thenから追加します */
      /** cloud firestoreのコレクション */
      const colRef = doc(db, 'users', userCredential.user.uid)
      /** document追加 */
      await setDoc(colRef, {
        uid: userCredential.user.uid,
        email: userCredential.user.email,
      })
      return userCredential.user
    })
    if (user) {
      result = true
    }
  } catch (error) {
    result = false
    console.log(error)
  }
  return result
}
```
別ユーザの情報で、再度新規登録してみます
![](https://storage.googleapis.com/zenn-user-upload/115579d5b18e-20230903.png)

コンソール、authentication、firestoreで確認してみましょう！
### コンソール
![](https://storage.googleapis.com/zenn-user-upload/a450f6bedbbe-20230903.png)

### authentication
![](https://storage.googleapis.com/zenn-user-upload/984909c9c88e-20230903.png)

### firestore
![](https://storage.googleapis.com/zenn-user-upload/9a8832510021-20230903.png)

全て新規登録されていますね！これで、新規作成処理は完了です！次はログイン処理を確認してましょう！

# ログイン処理
下記の情報でフォームを入力します。
![](https://storage.googleapis.com/zenn-user-upload/6d9cad564369-20230903.png)
コンソールで「ログイン成功」が表示されました！これで完了です！
![](https://storage.googleapis.com/zenn-user-upload/14ed26a13b00-20230903.png)

# おわりに
これでフォームからユーザの新規登録、ログイン処理までの流れを実装できました！
auth周りの最低限の機能はこれで全てになります！ログアウトについても、処理自体は掲載しているので、ボタンを配置して、処理を呼び出せばログアウトすることができます。
後編では、authのエラーハンドリングやバリデーションエラーについて実装、説明していきます！

最後まで読んでいただきありがとうございました！