Next.js × TypeScript × Firebase AuthenticationでGoogle認証を実装する

# 概要
Next.js × TypeScript × Firebase Authenticationを用いたgoogle認証機能を実装してみました。


# 実装（Firebase側）
## プロジェクトを作成する
[Firebase](https://firebase.google.com/?hl=ja)にアクセスし、新しいプロジェクトを作成します。
作成が完了したら、『ウェブ』というアイコンをクリックし、アプリ名の登録を行います。

![スクリーンショット 2020-09-13 15.00.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417980/4b9ce554-d16e-bc2d-1ff2-a646151772f4.png)

![スクリーンショット 2020-09-13 15.01.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417980/6d89d7b5-09f4-0fb1-54b7-7713b4967556.png)


アプリ名の登録が完了すると、下記のようなスクリプトが表示されるかと思います。
こちらはnextアプリ側で使うので、コピーしておきます。

```js
var firebaseConfig = {
    apiKey: xxxxx
    authDomain: xxxxx,
    databaseURL: xxxxx,
    projectId: xxxxx,
    storageBucket: xxxxx,
    messagingSenderId: xxxxx,
    appId: xxxxx,
    measurementId: xxxxx
};
```

## Google認証を有効にする
[Firebase](https://firebase.google.com/?hl=ja)のコンソール画面のSign-in methodタブにあるログインプロバイダの中でGoogle認証を有効化・無効化する部分があるので、まずそちらで有効化します。


![スクリーンショット 2020-09-13 14.56.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/417980/15ce8876-b5d0-632b-3120-2f4383afc4e4.png)

これでFirebase側での設定は終了です。

# 実装(Next.js側)

## インストール
firebase Authentication機能を利用したいので、
アプリケーションディレクトリにてfirebaseをインストールします。

```bash:myapp
$ yarn add firebase
```

## 必要ファイルを生成

### .env
アプリケーションディレクトリにて`.env`を生成します。

```bash:myapp
$ touch .env
$ vim .env
```

`.env`にコピーしたスクリプトの値を記述していきます。
各個人、値が違うので適宜記述してください。

```bash:myapp/.env
FIREBASE_API_KEY=xxxxx
FIREBASE_AUTH_DOMAIN=xxxxx
FIREBASE_DATABASE_URL=xxxxx
FIREBASE_PROJECT_ID=xxxxx
FIREBASE_STORAGE_BUCKET=xxxxx
FIREBASE_MESSEGING_SENDER_ID=xxxxx
FIREBASE_APP_ID=xxxxx
FIREBASE_MEASUREMENT_ID=xxxxx

```

**設定し終えたら、.gitignoreに.envがpushされないように追加を忘れないでください。**

### next.config.js
.envの値をnextアプリでも利用できるように`next.config.js`に記述していきます。

```js:myapp/next.config.js
module.exports = {
  env: {
    FIREBASE_API_KEY: process.env.FIREBASE_API_KEY,
    FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN,
    FIREBASE_DATABASE_URL: process.env.FIREBASE_DATABASE_URL,
    FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
    FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET,
    FIREBASE_MESSEGING_SENDER_ID: process.env.FIREBASE_MESSEGING_SENDER_ID,
    FIREBASE_APP_ID: process.env.FIREBASE_APP_ID,
    FIREBASE_MEASUREMENT_ID: process.env.FIREBASE_MEASUREMENT_ID,
  },
}

```

### Firebase.ts

.envの値を利用し、Firebase.tsに設定を記述していきます。

```ts:myapp/src/utils/Firebase.ts

import firebase from 'firebase'

const config = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  databeseURL: process.env.FIREBASE_DATABASE_URL,
  projectId: process.env.FIREBASE_PROJECT_ID,
  storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIREBASE_MESSEGING_SENDER_ID,
  appId: process.env.FIREBASE_APP_ID,
  measurementId: process.env.FIREBASE_MEASUREMENT_ID,
}
// firebase.appsをチェックし、ロードされているかどうかを確認。
// なければinitializeAppを実行
if (!firebase.apps.length) {
  firebase.initializeApp(config)
}

export default firebase
```

### Auth.tsxでContextの作成
ユーザー情報の値をグローバルに持ち、バケツリレーを回避しつつ、Auth.tsx配下のコンポーネントたちに値が渡るようにContextの作成を行います。

```tsx:src/context/Auth.tsx
import { User } from 'firebase';
import { FC, createContext, useEffect, useState } from 'react';

import firebase from '../utils/Firebase';

type AuthContextProps = {
  currentUser: User | null | undefined
}

const AuthContext = createContext<AuthContextProps>({ currentUser: undefined });

const AuthProvider: FC = ({ children }) => {
  const [currentUser, setCurrentUser] = useState<User | null | undefined>(
    undefined
  );

  useEffect(() => {
    // ログイン状態が変化するとfirebaseのauthメソッドを呼び出す
    firebase.auth().onAuthStateChanged((user) => {
      setCurrentUser(user);
    })
  }, []);

  /* 下階層のコンポーネントをラップする */
  return (
    <AuthContext.Provider value={{ currentUser: currentUser }}>
      {children}
    </AuthContext.Provider>
  )
}

export { AuthContext, AuthProvider }

```

_app.tsxにてComponentをAuthProviderでラップすることを忘れないでください。

```tsx:src/pages/_app.tsx

import React, { useEffect } from 'react'
import { AppProps } from 'next/app'
import { AuthProvider } from '../context/Auth'

const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
  return (
    ---略---
     <AuthProvider>
       <Component {...pageProps} />
     </AuthProvider>
    ---略---
  )
}

export default MyApp

```

### サインイン画面
それでは実際にサインアップ画面の実装を行っていきます。
尚、今回はUI側の実装は簡易的なものになりますので、UI実装は適宜実装してください。

```tsx:src/signIn.tsx
import { FC, useEffect, useContext } from 'react';
import Router from 'next/router';
import firebase from '../../utils/firebase';
import { AuthContext } from '../../context/Auth';

const SignIn: FC = () => {
  const { currentUser } = useContext(AuthContext);

  useEffect(() => {
    currentUser && Router.push('/')
  }, [currentUser]);

  const login = () => {
    const provider = new firebase.auth.GoogleAuthProvider();
    firebase.auth().signInWithRedirect(provider);
  }
  return (
     <div className="container">
      <button onClick={login}>googleでログインする</button>
     </div>
  )
}
```

- `useContext`によって親よりすべての子たちが`currentUser`という現在ログインしているユーザー情報の値を利用できるようにしています。
`useState`と合わせて利用することでpropsによりバケツリレーを行うことなく、情報更新や値渡しが可能になり、複雑にならずに済むことがメリットです。

- `useEffect`によってもし現在クライアントがログインしていた場合、ホーム画面へリダイレクトするようにしています。`[currentUser]`の部分は`currentUser`の値が変更された場合、`useEffect`が発火するようにしています。

# 動作確認

実装が完了したら、ホーム画面でcurrentUserのメールアドレスなどを表示させてみましょう。


# 参考リンク
- [Typescript×React×Hooksで会員管理②Contextでアプリの状態管理](https://qiita.com/mtitg/items/3f1b6e61cee2f01f04a8)
- [ReactでFirebase Authenticationを使う](https://qiita.com/zaburo/items/801bd288cec47bd28764)