Reactを基本からまとめてみた【20】【Firebase v9を使ったログイン機能の実装②】

##Firebaseのサイトに移動

公式サイト :　[Firebase]
(https://firebase.google.com/?hl=ja)

##プロジェクトの作成

#####① 画面右上のコンソールへ移動のボタンをクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/642a2390-0ade-3652-d7ec-d76d1451c2fa.png)

#####②プロジェクトを追加をクリック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/550c4066-a64c-d378-1de3-0f16cd9e5e70.png)

#####③プロジェクトの名前を付ける

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/e173b62c-f221-3fbc-866e-7d8f1b3cd303.png)

#####④Google アナリティクスを無効にする
このプロジェクトでGoogleアナリティクスを有効にするが『ON』になっているが
利用しないので『OFF』に設定して”プロジェクトを作成”ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/e0a1afb7-576d-8750-a42c-2569f6b4ac82.png)

#####⑤プロジェクト作成ボタンを押し、作成できたら続行ボタンを押す

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/8b523e26-512f-a071-b8e4-40a23067a05b.png)

#####プロジェクトの準備完了
プロジェクトの概要ページが表示される。これでプロジェクトの作成は完了。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/9af95db0-5b76-2693-e745-2c68c9cba526.png)

##Realtime Databaseを作成

#####① 構築『Realtime Database』をクリックする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/0993b021-8ce6-ffbd-662f-422613fee497.png)

#####② ページ内の「データベースを作成」ボタンをクリックする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/769f27b7-c9c9-42fd-ba5e-d74a1d2a70db.png)

#####③ クリックすると、「データベースの設定」というウインドウが表示され、ウインドウ内では、「データベースのオプション」が表示されるので、今回は「Realtime Database のロケーション」で「米国」を選択し、「次へ」ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/6776e04f-cf96-3fe8-b355-0d3d0c3487a5.png)

#####④ クリックすると、「セキュリティ ルール」が表示され、「セキュリティ ルール」では、「ロックモードで開始」か「テストモードで開始」のいずれか選択する。
#####今回は「テストモードで開始」を選択し、「有効にする」ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/5ae69d77-b720-e2a8-5292-ded5ddc03cb6.png)

#####これでデータベースの作成は完了。
クリックすると、データベースの作成が開始され、表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/cba38a90-9981-cf5f-07c2-492e434ae4bf.png)

##アプリのセットアップ
#####① Firebaseを追加画面のウェブ</>をクリック

ReactからFirebaseのサービスに接続するための認証情報が必要になるのでプロジェクトの概要画面の左から3番目のボタンをクリックしてアプリの登録を行う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/02d50b0d-4114-088f-0399-fdc88363d98f.png)

#####② アプリのニックネームの登録
アプリの登録を行うためニックネームの設定を行う必要があり、任意の名前をつける。設定したら”アプリ登録”ボタンをクリックする。
#####＊『このアプリのFirebase Hosutingも設定します』にチェックを入れない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/b0aa40d9-f27c-394a-4d87-f38a6b8de209.png)

『アプリの登録』ボタンをクリックするとFirebaseに接続する為の情報が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/0c842998-02d1-59db-fad7-a92cdcfccafd.png)

#####※ 右下の『クリップボード』ボタンを押して、コピーしたものを何かに貼り付けておく。
#####③ コピーしたソースコードが紛失した際の確認方法
Firebaseコンソールの左側に「プロジェクトの概要」で歯車のマークをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/7d6460df-78e8-913a-3b02-b5c5ab697ecf.png)

クリックすると、メニューが表示されるので「プロジェクトを設定」をクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/ff9fb60b-a651-ab83-9ad8-f27e607b22c5.png)

クリックすると、「Firebase SDK snippet」という項目があり、表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/ec2570ea-5e58-b57b-6069-1aabd702a232.png)

##Authenticationを有効化
#####左メニューのAuthenticationを選択し、ログイン情報でメール/パスワードを有効にする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/ec87778e-cc01-8d07-e847-a91384e45b6a.png)


##Firestoreの有効化/設定
#####左メニューのDatabaseを選択し、データベースの作成を選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/051b928b-236f-d6c2-7278-f7d5db8ca965.png)

#####① データベースを作成する。
一旦テストモードで作成する。ルールは後で作る。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/cb3c80c8-66a5-7b21-f34d-2c0e9e21274b.png)

#####② コレクションを追加する。
コレクションを追加するためにテストデータを１件登録しておく。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/821f5a53-4455-6d85-0400-51b415c5d814.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/ec5b48aa-0aab-22f5-2597-069cb463339d.png)





##アプリをセットアップ

#####① firebase.jsをsrcフォルダの下に作成し、編集する。

```
src
 ├── firebase.js
```
```
$ touch src/firebase.js
```

#####② SDKの『const firebaseConfig』のソースコードを追加する

```firebase.js
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_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_MESSAGE_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
};

export const firebaseApp = initializeApp(firebaseConfig);
export const auth = getAuth(firebaseApp);
export const firestore = getFirestore(firebaseApp);
```

#####③表示されている情報はReactで環境変数として利用するため作成したReactプロジェクトフォルダの直下に.env.localファイルを作成し、下記のソースコードを入力する。
※ " "の中身は、firebaseConfigを確認して入力する。


```
$ touch .env
```
```
//.env
REACT_APP_FIREBASE_API_KEY="",
REACT_APP_FIREBASE_AUTH_DOMAIN="",
REACT_APP_FIREBASE_DATABASE="https://<PROJECT_ID>.firebaseio.com",
REACT_APP_FIREBASE_PROJECT_ID="",
REACT_APP_FIREBASE_STORAGE_BUCKET="",
REACT_APP_FIREBASE_MESSAGE_SENDER_ID="",
REACT_APP_FIREBASE_APP_ID=""


######//REACT_APP_FIREBASE_DATABASE="https://<PROJECT_ID>.firebaseio.com",の　<PROJECT_ID>は、
下記のREACT_APP_FIREBASE_PROJECT_ID="",の""を記載すること
```

#####④ .gitignoreファイルに.envを追加
.gitignoreファイルに.envを追加することでgithubにアップロードされない。

```
//省略
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.env
//省略
```

#####⑤ firebaseパッケージをインストールする。
firebase.jsファイルでは、firebaseに接続するために必要なfirebaseと認証に必要なfirebase/authをimportする。firebaseをimportしていますがReactにデフォルトから含まれているわけではないのでfirebaseパッケージのインストールが必要である。
プロジェクトフォルダ直下でnpmコマンドによりfirebaseパッケージをインストールする。

```
$ npm install --save firebase
$ npm i firebase@9.5.0
```

#####⑥ コンソール画面に戻るを押す

##Firebaseの認証の設定

#####①Firebaseの認証(Authentication)の設定を行う。

プロジェクトの概要ページから中央にあるAuthentication（ユーザの認証と管理)をクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/875ade5f-f5a2-537e-ff35-8d08e308f384.png)

#####②プロジェクトの概要から認証をクリック
Authenticationのページが表示されるので”始まる”ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/d5c83e5e-5f9f-1a47-0670-47a571bf56ab.png)


#####③Authenticationページを編集する
ユーザがサインイン(ログイン)するための方法の一覧が表示される。Googleアカウントなども利用することができるが、今回はメール/パスワードを利用する。

※ 理由：一般的なサービスではユーザ登録としてメールアドレスを登録してもらうことが多いから　
メールアドレスとパスワードを使用して登録を有効にしてください。有効にしたら”保存”ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/87453456-a070-9419-6691-54d70780869f.png)

メール/パスワードでの設定
保存後はメール/パスワードのステータスのみ”有効”になっていることが確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/c2558886-d4f1-405c-d6a3-3276e72605cf.png)


##ログイン機能の実装
#####①SignInページを作成する。

```
$ touch src/components/SignIn.js
```
#####②material-ui SignInページのコードを貼り付ける。
該当するpageの『source code』をクリックし、今回は、『SignIn.js』をクリックし、ソースをコピーして、SignIn.jsに貼り付ける。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/acde9973-5be4-1a47-3bc8-cc689b09858c.png)

#####③App.jsにimportを追加する。

```App.js
import SignIn from "./components/SignIn";
```



#####①『auth』ディレクトリを作成し、こちらに認証機能系は集約する。

```
src
 ├── auth
    ├── AuthProvider.js
    └── Login.js
    └── PrivateRoute.js
    └── SignUp.js
 ├── components
 ├── firebase
 ├── App.js
```
```
$ mkdir src/auth
$ touch src/auth/AuthProvider.js
$ touch src/auth/Login.js
$ touch src/auth/PrivateRoute.js
$ touch src/auth/SignUp.js
```

#####② App.jsにログイン状態で表示ページを変える為、Routerを作成する。
exactはpathが「含む」とならないように指定。


```src/App.js
import React from "react";
import ReactDOM from "react-dom";
import './App.css';
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { Provider } from "react-redux";
import { AuthProvider } from "./auth/AuthProvider";
import Home from "./components/Home";
import Login from "./auth/Login";
import SignUp from "./auth/SignUp";

const App = () => {
  return (
   <React.StrictMode>
    <Router>
      <Provider store={store}>
        <AuthProvider>
          <Routes>
            <Route path="/" element={<App />} />
            <Route path="/login" element={<Login />} />
            <Route path="/signup" element={<SignUp />} />
          </Routes>
        </AuthProvider>
      </Provider>
    </Router>
  </React.StrictMode>,
  );
};
export default App;
```

#####③ AuthPrivider.jsを編集する。
認証の情報（ユーザーがログイン、サインアップする）は、このファイルで作成する。ユーザー情報が必要なコンポーネントでuseContextを使用する。
* 通常データはトップダウン形式でpropsを渡さないといけないですが、contextを使うことで、コンポーネントツリーに簡単にデータを共有することができる。


```src/auth/AuthProvider.js
import React, { useEffect, useState } from "react";
import { auth } from "../firebase";
import {
  createUserWithEmailAndPassword,
  onAuthStateChanged,
  signInWithEmailAndPassword,
  updateProfile,
} from "firebase/auth";

const AuthContext = React.createContext();

const AuthProvider = ({ children }) => {
  const [currentUser, setCurrentUser] = useState(null);

  //サインアップ後認証情報を更新
  const signup = async (name, email, password, navigate) => {
    try {
      // メアドとパスワードからユーザを作成
      const res = await createUserWithEmailAndPassword(auth, email, password);

      // 作成したユーザにdisplaynameをセット
      updateProfile(res?.user, {
        displayName: name,
      });

      onAuthStateChanged(auth, (user) => {
        setCurrentUser(user);
      });

      // メイン画面へ移動
      navigate("/");
    } catch (error) {
      alert(error);
    }
  };
  //ログインさせる
  const login = async (email, password, navigate) => {
    try {
      await signInWithEmailAndPassword(auth, email, password);
      onAuthStateChanged(auth, (user) => setCurrentUser(user));
      navigate("/");
    } catch (error) {
      alert(error);
    }
  };

  //初回アクセス時に認証済みかチェック
  useEffect(() => {
    onAuthStateChanged(auth, setCurrentUser);
  }, []);

  return (
    <AuthContext.Provider value={{ signup, login, currentUser }}>
      {children}
    </AuthContext.Provider>
  );
};
export { AuthContext, AuthProvider };
```

初期値のユーザーのステートはnull。

#####signup関数
引数にemail、password、historyを渡して非同期処理を行う。
auth.createUserWithEmailAndPassword(email, password)は、
firebaseのメソッドでemailとpasswordを元にアカウントが作成される。
その後userの情報を取得し、CurrentUserにセットする。

######history.push（"/")は、reactRouterの画面遷移させる機能で、今回はログイン後、Home画面に行くようにする。

#####login関数
同じ様に、ユーザーがログインしたら情報を取得しCurrentUserを更新するようにする。
auth.signInWithEmailAndPassword(email,password)
これもまたfirebaseのメソッドである。

あとは、最初にログインしてるか確認する為、useEffectで一回だけauth.onAuthStateChangedを実行する。
※一回だけ実行したいので、第二引数には空の配列[]を渡します。

#####④ PrivateRoute.jsを編集する。
アプリのメイン画面Home.jsは、PrivateRouteに指定する。
ここで、ユーザーが『ログインしてれば => メイン画面を表示』
『未ログインの場合 => ログイン画面を表示』の作業を行なう。

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

const PrivateRoute = ({ component, ...rest }) => {
  const { currentUser } = useContext(AuthContext);
  //AuthContextからcurrentUserを受け取る

  const renderingComponent = currentUser ? component : Login;
  //currentUserがtrueの場合component＝Home、falseならLoginコンポーネントにroute

  return <Route {...rest} component={renderingComponent} />;
};

export default PrivateRoute;
```

...restは、残りのpropsをまとめて引数に渡す（今回、他のpropsはない）

#####⑤SignUp.jsとLogin.jsを編集する。
SignUp.jsコンポーネントでは、ユーザー登録画面の表示、登録内容を取得する。
handleSubmitが実行される時、入力されたemailとpasswordの内容をAuthProviderで作ったsignup関数の引数に渡してデータが登録される。
アップデート後のhistory（情報）を渡すために、最後exportの時withRouter(SignUp)を使う。

```src/auth/SignUp.js
import React, { useContext, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import "firebase/auth";
import { AuthContext } from "./AuthProvider";
import { Box, Button, styled, TextField } from "@material-ui/core";

const SignUpButton = styled(Button)({
  background: "#f16272",
  fontSize: "1.0rem",
  border: 0,
  borderRadius: 3,
  color: "white",
  padding: "10px 40px",
  marginTop: "30px",
  "&:hover": {
    backgroundColor: "#ee3e52",
  },
});

const SignUp = ({ history }) => {
  const { signup } = useContext(AuthContext);
  //AuthContextからsignup関数を受け取る
  const navigate = useNavigate();

  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = (event) => {
    event.preventDefault();
    signup(name, email, password, navigate);
  };

  return (
    <div className="wrapper">
      <div className="auth-container">
        <div style={{ textAlign: "center" }}>
          <h1>新規登録</h1>
          <form className="auth-form" onSubmit={handleSubmit}>
            <div className="auth-form-item">
              <Box
                component="form"
                sx={{
                  "& > :not(style)": { m: 1, width: "25ch" },
                }}
                noValidate
                autoComplete="off"
              >
                <TextField
                  margin="normal"
                  required
                  fullWidth
                  id="Display name"
                  label="Display name"
                  name="display name"
                  autoComplete="display name"
                  autoFocus
                  variant="outlined"
                  value={name}
                  onChange={(e) => {
                    setName(e.currentTarget.value);
                  }}
                />
              </Box>
            </div>
            <div className="auth-form-item">
              <Box
                component="form"
                sx={{
                  "& > :not(style)": { m: 1, width: "25ch" },
                }}
                noValidate
                autoComplete="off"
              >
                <TextField
                  margin="normal"
                  required
                  fullWidth
                  id="email"
                  label="Email Address"
                  name="email"
                  autoComplete="email"
                  autoFocus
                  variant="outlined"
                  value={email}
                  onChange={(e) => {
                    setEmail(e.currentTarget.value);
                  }}
                />
              </Box>
            </div>
            <div className="auth-form-item">
              <Box
                component="form"
                sx={{
                  "& > :not(style)": { m: 1, width: "25ch" },
                }}
                noValidate
                autoComplete="off"
              >
                <TextField
                  margin="normal"
                  required
                  fullWidth
                  name="password"
                  label="Password"
                  type="password"
                  id="password"
                  autoComplete="current-password"
                  variant="outlined"
                  value={password}
                  onChange={(e) => {
                    setPassword(e.currentTarget.value);
                  }}
                />
              </Box>
            </div>
            <SignUpButton className="signUp-btn" type="submit">
              新規登録する
            </SignUpButton>
          </form>
          <Link to="/login" className="auth-bottom">
            アカウントをお持ちの方はこちら
          </Link>
        </div>
      </div>
    </div>
  );
};

export default SignUp; 
```

Login.jsも同様に、signup部分をloginに変えて編集する。

```src/auth/Login.js
import React, { useContext, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { AuthContext } from "./AuthProvider";
import { Box, Button, styled, TextField } from "@material-ui/core";

const SignInButton = styled(Button)({
  background: "#6fc4f9",
  fontSize: "1.0rem",
  border: 0,
  borderRadius: 3,
  color: "white",
  padding: "10px 40px",
  marginTop: "30px",
  "&:hover": {
    backgroundColor: "#57baf8",
  },
});

const Login = ({ history }) => {
  const { login } = useContext(AuthContext);
  //AuthContextからlogin関数を受け取る
  const navigate = useNavigate();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = (event) => {
    event.preventDefault();
    login(email, password, navigate);
  };

  return (
    <div className="wrapper">
      <div className="auth-container">
        <div style={{ textAlign: "center" }}>
          <h1>LogIn</h1>

          <form className="auth-form" onSubmit={handleSubmit}>
            <div className="auth-form-item">
              <Box
                component="form"
                sx={{
                  "& > :not(style)": { m: 1, width: "25ch" },
                }}
                noValidate
                autoComplete="off"
              >
                <TextField
                  margin="normal"
                  required
                  fullWidth
                  id="email"
                  label="Email Address"
                  name="email"
                  autoComplete="email"
                  autoFocus
                  variant="outlined"
                  value={email}
                  onChange={(e) => {
                    setEmail(e.currentTarget.value);
                  }}
                />
              </Box>
            </div>
            <div className="auth-form-item">
              <Box
                component="form"
                sx={{
                  "& > :not(style)": { m: 1, width: "25ch" },
                }}
                noValidate
                autoComplete="off"
              >
                <TextField
                  margin="normal"
                  required
                  fullWidth
                  name="password"
                  label="Password"
                  type="password"
                  id="password"
                  autoComplete="current-password"
                  variant="outlined"
                  value={password}
                  onChange={(e) => {
                    setPassword(e.currentTarget.value);
                  }}
                />
              </Box>
            </div>
            <SignInButton type="submit">LOGINする</SignInButton>
          </form>
          <Link to="/signup" className="auth-bottom">
            アカウントをお持ちでない方はこちら
          </Link>
        </div>
      </div>
    </div>
  );
};

export default Login;
```




##参考サイト
[Reactを基本からまとめてみた【13】【Firebaseを使ったログイン機能の実装 ①】]
(https://qiita.com/kanfutrooper/items/e2d1ce3efbbd0d6908ea)
[ReactHooks + Firebase(Authentication, Firestore)でTodoアプリ作る]
(https://qiita.com/k_tada/items/ed05d14458d1ddfcefae)
[React Firebase Authentication Crash Course With Context API and Protected Routes For Beginners]
(https://www.youtube.com/watch?v=6kgitEWTxac)