【React】 Firebaseを使用してEmail・パスワードでの認証機能の実装

# 概要
ReactでFirebaseを使用した簡単な認証の実装方法に関してまとめました。
あくまでも、実装の流れをメインとしており、各機能の詳しい説明等は省いております。

# Firebaseのセットアップ

## 新しいプロジェクトの作成
まずはFirebaseのConsoleで新しいProjectの作成を行いましょう。

![image](https://user-images.githubusercontent.com/32632542/67456573-2d64ea80-f66c-11e9-8459-791729788236.png)

## Authenticationの使用

`Authentication`の画面にて、 `SET UP SIGN-IN METHOD`をクリックしましょう。

![image](https://user-images.githubusercontent.com/32632542/67456662-76b53a00-f66c-11e9-8743-a4e53c281159.png)

そして、何を使用して認証を行いたいのか選択しましょう。
本投稿でははEmailを使用して行うので、`Email/Password`を許可します。

![image](https://user-images.githubusercontent.com/32632542/67456757-b2e89a80-f66c-11e9-9081-ce215da139bf.png)

## credentialsの取得
左端にあるセッティングボタンから、`project settings`を選択し、アプリの`credentials`を取得しましょう。
(`project settings`の下の方にIOSかAndroidかWebのボタンがあるのでWebのボタンをクリックします。)

![image](https://user-images.githubusercontent.com/32632542/68387457-b36f4e00-01a1-11ea-8b9b-f932972a877d.png)

`credentials`は下記のような形式で取得可能です。

```
apiKey: "your_key",
authDomain: "your_app_id.firebaseapp.com",
databaseURL: "https://your_app_id.firebaseio.com",
projectId: "your_app_id",
storageBucket: "your_storage_bucket",
messagingSenderId: "sender_id",
appId: "your_app_id"
```

# Reactアプリケーションのセットアップ

`create-react-app`を使用して、アプリケーションを作成します。

```
$ create-react-app your-app-name
$ cd your-app-name
```

今回の実装で必要となる2つのパッケージをインストールします。

```
$ yarn add firebase react-router react-router-dom
```

## ReactアプリをFirebaseと繋ぐ

先ほど取得した`firebase credentials`を下記の形式で`.env`に貼り付けましょう。

```
// .env
REACT_APP_FIREBASE_KEY="your_key"
REACT_APP_FIREBASE_DOMAIN="your_app_id.firebaseapp.com"
REACT_APP_FIREBASE_DATABASE="https://your_app_id.firebaseio.com"
REACT_APP_FIREBASE_PROJECT_ID="your_app_id"
REACT_APP_FIREBASE_STORAGE_BUCKET="your_storage_bucket"
REACT_APP_FIREBASE_SENDER_ID="sender_id"
```

新しく`src/base.js`を作成し、下記のコードのようにすることで、与えられた`credentials`でfirebaseのインスタンスを作成します。

```js
import * as firebase from "firebase/app";
import "firebase/auth";

export const app = firebase.initializeApp({
  apiKey: process.env.REACT_APP_FIREBASE_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
  databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID
});
```

## Contextの設定

`src/auth/AuthProvider`を作成し、認証に必要なロジックをここに集約させます。
そして、集約した認証に必要な情報をコンポーネントツリー全体に渡すためにReactの`Context`を使用します。

- Reactの`Contex`に関しては[こちら](https://reactjs.org/docs/context.html)で詳しい説明がされています。
- 下記コード内の`signInWithEmailAndPassword`等は`firebase`から提供されている関数で[公式ドキュメント](https://firebase.google.com/docs/auth/web/firebaseui)で細かい動作等を確認頂けます。

```jsx
import React, { useEffect, useState } from "react";
import { app } from "../base.js";

// contextの作成
export const AuthContext = React.createContext();

export const AuthProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(null);
  
  // ユーザーをログインさせる関数
  const login = async (email, password, history) => {
    try {
      await app.auth().signInWithEmailAndPassword(email, password);
      history.push("/");
    } catch (error) {
      alert(error);
    }
  };
  
  // 新しいユーザーを作成しログインさせる関数
  const signup = async (email, password, history) => {
    try {
      await app.auth().createUserWithEmailAndPassword(email, password);
      history.push("/");
    } catch (error) {
      alert(error);
    }
  };

  useEffect(() => {
    app.auth().onAuthStateChanged(setCurrentUser);
  }, []);

  return (
    // Contextを使用して認証に必要な情報をコンポーネントツリーに流し込む。
    <AuthContext.Provider
      value={{
        login: login,
        signup: signup,
        currentUser
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};
```

## Private Routeの作成

認証を許可されたユーザーのみがアクセスできる`Private Route`を作成します。
新しく`src/auth/PrivateRoute`を作成し、下記のコードのように設定します。
`AuthContext`から渡された`currentUser`がセットされていればアクセスを許可し、セットされていない(`null`)の場合は`Login`ページに移動されます。

```jsx
import React, { useContext } from "react";
import { Route } from "react-router-dom";
import { AuthContext } from "./AuthProvider";
import Login from "./Login";

const PrivateRoute = ({ component: RouteComponent, ...options }) => {
  const { currentUser } = useContext(AuthContext);
  const Component = currentUser ? RouteComponent : Login;

  return <Route {...options} component={Component} />;
};

export default PrivateRoute;
```

## ルーティングの設定

`src/App.js`を開き、ルーティングを設定します。
この場合、`Home`を`PrivateRoute`に設定しているので、`Home`にアクセスするのにユーザーの認証を必要としています。

`Home`,`Login`,`SignUp`コンポーネントはこれから作成します。

 ```js
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import PrivateRoute from "./auth/PrivateRoute";
import { AuthProvider } from "./auth/AuthProvider";
import Home from "./components/Home";
import Login from "./auth/Login";
import SignUp from "./auth/SignUp";

const App = () => {
  return (
    <AuthProvider>
      <Router>
        <div>
          <PrivateRoute exact path="/" component={Home} />
          <Route exact path="/login" component={Login} />
          <Route exact path="/signup" component={SignUp} />
        </div>
      </Router>
    </AuthProvider>
  );
};

export default App;
```


## `Home`, `Login` and `Signup`コンポーネントの作成.

各コンポーネントを下記のように作成していきます。

### src/components/Home.jsx

```jsx
import React from "react";
import { app } from "../base";

function Home(props) {
  return (
    <div>
      <h2>Home Page</h2>
      // ユーザーをログアウトさせる
      <button onClick={() => app.auth().signOut()}>Sign out</button>
    </div>
  );
}

export default Home;
```

### src/auth/Login.jsx

```jsx
import React, { useContext } from "react";
import { withRouter } from "react-router";
import { AuthContext } from "./AuthProvider";

const Login = ({ history }) => {
  const { login } = useContext(AuthContext);

  // AuthContextからlogin関数を受け取る
  const handleSubmit = event => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    login(email.value, password.value, history);
  };

  return (
    <div>
      <h1>Log in</h1>
      <form onSubmit={handleSubmit}>
        <label>
          Email
          <input name="email" type="email" placeholder="Email" />
        </label>
        <label>
          Password
          <input name="password" type="password" placeholder="Password" />
        </label>
        <button type="submit">Log in</button>
      </form>
    </div>
  );
};

export default withRouter(Login);
```

### src/auth/Signup.jsx

```jsx 
import React, { useContext } from "react";
import { withRouter } from "react-router";
import { AuthContext } from "./AuthProvider";

const SignUp = ({ history }) => {
  const { signup } = useContext(AuthContext);
  // AuthContextからsignup関数を受け取る
  const handleSubmit = event => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    signup(email.value, password.value, history);
  };

  return (
    <div>
      <h1>Sign up</h1>
      <form onSubmit={handleSubmit}>
        <label>
          Email
          <input name="email" type="email" placeholder="Email" />
        </label>
        <label>
          Password
          <input name="password" type="password" placeholder="Password" />
        </label>
        <button type="submit">Sign Up</button>
      </form>
    </div>
  );
};

export default withRouter(SignUp);
```

# アプリケーションの動作確認

アプリケーションを立ち上げます。

```
$ yarn start
```

## Home画面アクセスに認証が必要である事の確認
 
`http://localhost:3000`にアクセスしようとすると下記のようにLoginページに飛ばされる事を確認します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/134a1e2f-5fde-f38e-0a74-20ca216b90de.png)

## ユーザーが作成できる事の確認
`http://localhost:3000/signup`で`email`と`password`を入力し、`Sing Up`をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/a88d3c28-0ea1-e78a-17e6-b4c95de21a56.png)

自身のFirebaseコンソールで作成したユーザーが登録されている事を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/b2c7a05d-be53-8257-b7b1-3c5b99fa1bf3.png)

## 作成したユーザーでログインできる事の確認
`http://localhost:3000/login`で先ほど作成したユーザーの情報でログインし、`Home`画面にアクセスできる事を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/258219/264fa705-7ff5-1b06-9729-1e71f053dea8.png)


# 参考
- [Firebase React Authentication Tutorial](https://www.youtube.com/watch?v=unr4s3jd9qA&t=330s)
- [create-react-app with TypeScript & Firebase Authentication [JavaScript] \| No Programming, No Life!](https://blog.morizyun.com/javascript/typescript-tutorial-react-app-firebase-authentication.html)
- [Firebase React Authentication Tutorial](https://maksimivanov.com/posts/firebase-react-tutorial/)