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

##使用する技術

- React Router
- useEffect
- useState
- useContext
- useRef

##Reactプロジェクトの作成

```
% npx create-react-app <プロジェクト名>
```

##Firebaseの初期設定

公式サイト：[Firebase]
(https://firebase.google.com/)

#####1. 画面中央にある”プロジェクトを作成”ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/271b40c5-815a-3f42-84f5-65d60e9452af.png)


#####2. プロジェクトの名前をつける画面が表示されるので任意の名前をつける。
※ ここではreact-authというプロジェクト名をつける。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/fa8a7587-b175-df33-d9f1-ef98ef39840f.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/340785cc-9d86-473f-2f31-75eb4b7bf8e7.png)

② プロジェクトの作成が開始され、プロジェクトの作成が完了すると以下の画面が表示されるので、『続行』ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/0890560d-369d-67d4-4d65-65f559551215.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/f0bb4595-12ba-9486-6fcc-38068a534d43.png)

##プロジェクトの概要ページ

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/773d5f0a-8e8f-af42-1572-571a19cf438e.png)

②アプリの登録を行うためニックネームの設定を行う必要がある。任意の名前をつける。
※ ここではreact-authというニックネームを設定。
設定したら『アプリ登録』ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/1716d736-a039-d860-32d1-7f34a9a2b006.png)

③ ”アプリの登録”ボタンをクリックするとFirebaseに接続するための情報が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/8e48715f-e195-a422-7aa1-05bc9be4b814.png)


##Firebase SDKの追加

① 表示されている情報はReactで環境変数として利用するため作成したReactプロジェクトフォルダの直下に.env.localファイルを作成する。

```
$ touch .env.local
```
```
//.env.local
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を
srcフォルダの下に作成する。

```
$ touch src/firebase.js
```
```src/firebase.js
import firebase from 'firebase/app';
import 'firebase/auth';

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  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_SENDER_ID,
};

firebase.initializeApp(firebaseConfig);

export const auth = firebase.auth();
```

.env.localファイルの環境変数はprocess.env.環境変数名で取得することができる。

firebase.jsファイルでは、firebaseに接続するために必要なfirebaseと認証に必要なfirebase/authをimportしている。firebaseをimportしているがReactにデフォルトから含まれていないのでfirebaseパッケージのインストールが必要となる。

④ プロジェクトフォルダ直下でnpmコマンドによりfirebaseパッケージをインストールする。

```
% npm install --save firebase
```

##Firebaseの認証の設定

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/61e963c4-b190-8c2c-1669-a4d9ecd8b707.png)

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/3c218b44-8362-ae50-aa76-68c109db311f.png)

##Authenticationページ

ユーザがサインイン(ログイン)するための方法の一覧が表示される。
Googleアカウントなども利用することができるここでは一番上のメール/パスワードを利用する。
※ 一般的なサービスではユーザ登録としてメールアドレスを登録してもらうことが多い

① ステータス列を見るとデフォルトではすべて無効になっているので、有効にするために右側に表示されている”鉛筆”のアイコンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/1ccfad2c-08c9-04b1-99cd-6a416ac84f7e.png)

##サインイン方法の一覧
① メールアドレスとパスワードを使用して登録を有効にし、『保存』ボタンをクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/3354bde9-b35e-f314-87f9-b2693097ee3d.png)

##メール/パスワードでの設定

① 保存後はメール/パスワードのステータスのみ『有効』になっていることを確認する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/a9fdc72e-36c8-49e2-a04a-d0bff02e6ec7.png)

##ユーザ登録画面(サインアップ画面)の作成

① src/componentsフォルダを作成し、SingUp.jsファイルを作成する。

```src/components/SingUp.js
const SignUp = () => {
  return <h1>ユーザ登録</h1>;
};
export default SignUp;
```

②SignIn.jsファイルが作成できたらsrc直下にあるApp.jsファイルを以下のように編集する。App.jsファイルからSignUpコンポーネントをimportする。

```src/App.js
import SignUp from './components/SignUp';

function App() {
  return (
    <div style={{ margin: '2em' }}>
      <SignUp />
    </div>
  );
}

export default App;
```

##ユーザ登録画面
① SignUpコンポーネントにユーザ登録フォームを作成する。
登録フォームは通常のHTML文と同じで、登録ボタンをクリックするとhandleSubmit関数が実行される。handleSubmitの中ではevent.preventDefault()でsubmitイベントのデフォルトの動作を停止している。preventDefaultがない場合登録ボタンをクリックすると画面がリロードされる。

```src/components/SingUp.js
const SignUp = () => {
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('登録');
  };

  return (
    <div>
      <h1>ユーザ登録</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>メールアドレス</label>
          <input name="email" type="email" placeholder="email" />
        </div>
        <div>
          <label>パスワード</label>
          <input name="password" type="password" />
        </div>
        <div>
          <button>登録</button>
        </div>
      </form>
    </div>
  );
};

export default SignUp;
```

##input要素の値の取得

ユーザを登録するためにinput要素に入力した値を取得してFirebaseに送信する必要がある。入力した値を取得する方法は複数あり、その中から3つを説明する。
3つ方法を説明するがコードが短い最初の『event』からの取得を利用して認証機能を実装する。
※ 『console.log』で入力した値を表示できるようにしているので、実際に取得できるか確認すること。

#####1. eventから取得

① eventのtarget.elementsを使って入力した値を取得することができる。

```SignUp.js
const handleSubmit = (event) => {
  event.preventDefault();
  const { email, password } = event.target.elements;
  console.log(email.value, password.value);
};

```

#####2. useRefを利用

② React HookのuseRefを利用することで直接input要素から値を取得することができる。

```SignUp.js
import { useRef } from 'react';
const SignUp = () => {
  const emailRef = useRef(null);
  const emailPassword = useRef(null);
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log(emailRef.current.value, emailPassword.current.value);
  };

return (
    <div>
      <h1>ユーザ登録</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>メールアドレス</label>
          <input name="email" type="email" placeholder="email" ref={emailRef} />
        </div>
        <div>
          <label>パスワード</label>
          <input
            name="password"
            type="password"
            placeholder="password"
            ref={emailPassword}
          />
        </div>
        <div>
          <button>登録</button>
        </div>
      </form>
    </div>
  );
};

export default SignUp;
```

#####3. useStateを利用

① React HookのuseStateを利用してinput要素に入力した値を変数に保存してhandleSubmit実行時に保存した値を利用する。

```SignUp.js

import { useState } from 'react';
const SignUp = () => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log(email, password);
  };
  const handleChangeEmail = (event) => {
    setEmail(event.currentTarget.value);
  };
  const handleChangePassword = (event) => {
    setPassword(event.currentTarget.value);
  };

return (
    <div>
      <h1>ユーザ登録</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>メールアドレス</label>
          <input
            name="email"
            type="email"
            placeholder="email"
            onChange={(event) => handleChangeEmail(event)}
          />
        </div>
        <div>
          <label>パスワード</label>
          <input
            name="password"
            type="password"
            placeholder="password"
            onChange={(event) => handleChangePassword(event)}
          />
        </div>
        <div>
          <button>登録</button>
        </div>
      </form>
    </div>
  );
};
export default SignUp;
```

##Firebaseへのユーザの登録

フォームに入力した値を取得できることが確認できたのでfirebaseにユーザを登録する処理を追加する。ユーザの登録にはfirebaseのauth().createUserWithEmailAndPasswordメソッドを利用する。

作成済のfirebase.jsファイルでexportしているauthをSignUpコンポーネントでimportして利用する。
createUserWithEmailAndPasswordメソッドの引数には入力フォームから取得したメールアドレスとパスワードを指定する。

```SignUp.js
import { auth } from '../firebase';

const SignUp = () => {
  const handleSubmit = (event) => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    auth.createUserWithEmailAndPassword(email.value, password.value);
  };
//略
```

##メールアドレスとパスワードの入力
登録ボタンをクリックした後Firebaseの管理画面にアクセスを行い、AuthenticationのページからUsersのタブをクリ選択する。入力フォームで入力したユーザが登録されていることを確認する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/6fdd434b-7b46-b226-7d60-7076cff1adc9.png)

##ユーザ情報の共有(Context)

ユーザ登録が完了後は登録したユーザが現在ログインしているかどうかの情報をアプリケーション内で保持する必要があり、全てのコンポーネントでユーザ情報を共有するためにReact Hookの一つuseContextを利用する。

① srcフォルダの下にcontextフォルダとAuthContext.jsファイルを作成する。

ユーザ情報を含むuserを共有することが可能となる。

```src/context/AuthContext.js

import { createContext, useState, useContext } from 'react';

const AuthContext = createContext();

export function useAuthContext() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }) {
  const [user, setUser] = useState('');

  const value = {
    user,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
```

② AuthContext.jsファイルの中でユーザがサインイン、サインアウトを監視するメソッドonAuthStateChangedを設定する。

FirebaseではリスナーとしてonAuthStateChangedはサインイン、サインアウトが行われると実行され、サインインの場合はuserオブジェクトにuserに関する値を持つ。
サインアウトの場合はnullとなる。

```

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    // User is signed in.
  }
});
```
サインインした場合はuserが値を持つので、onAuthStateChangedとsetUserを組み合わせることでユーザがサインインした時のみユーザ情報を保存することができる。
React HookのuserEffectを利用し、マウント時にonAuthStateChangedを実行しサインイン/サインアウトを監視する。
useEffectの中の処理はAuthcontext.jsのマウント時に1度だけ実行させるために[]は忘れずに設定を行なう。アンマウント時はリスナーとして監視が必要なくなるため削除できるようonAuthStateChanged実行時に戻されるUnsubscribeを実行する。
削除しないとアンマウントしてもリスナーとして処理を継続する。
firebase.auth()はfirebase.jsをimportして利用する。

```src/context/AuthContext.js
import { createContext, useState, useContext, useEffect } from 'react';
import { auth } from '../firebase';

const AuthContext = createContext();

export function useAuthContext() {
  return useContext(AuthContext);
}

export function AuthProvider({ children }) {
  const [user, setUser] = useState('');

  const value = {
    user,
  };

  useEffect(() => {
    const unsubscribed = auth.onAuthStateChanged((user) => {
      setUser(user);
    });
    return () => {
      unsubscribed();
    };
  }, []);

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

//アンマウント時に行うリスナーの削除処理はreturn unsubscribedと記述することができる。()はつけない。
```
③ Contextを設定したらデータを共有するコンポーネントを作成したAuthProviderでWrap(包む)する。
App.jsファイルにAuthContextをimportとしてAuthProviderで包む。

```App.js
import SignUp from './components/SignUp';
//追加
import { AuthProvider } from './context/AuthContext';

function App() {
  return (
//追加
    <AuthProvider>
      <div style={{ margin: '2em' }}>
        <SignUp />
      </div>
//追加
    </AuthProvider>
  );
}

export default App;
```

ページをリフレッシュしてもonAuthStateChangedが実行されるのでconsole.logを使ってuserオブジェクトにアクセスできるか確認する。

```src/context/AuthContext.js
useEffect(() => {
  const unsubscribed = auth.onAuthStateChanged((user) => {
    console.log(user);
    setUser(user);
  });
  return () => {
    unsubscribed();
  };
}, []);
```

##userオブジェクトの確認
Contextの設定が完了し、Singupコンポーネントからもuserにアクセスすることが可能なのでSignupコンポーネントでuserにアクセスできるか確認する。
AuthContextからuseAuthContextをimportしてuserを取得します。
取得したuserからemailをブラウザ上に表示させています。

```src/context/AuthContext.js
import { auth } from '../firebase';
import { useAuthContext } from '../context/AuthContext';

const SignUp = () => {
  const { user } = useAuthContext();
  const handleSubmit = (event) => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    auth.createUserWithEmailAndPassword(email.value, password.value);
  };

return (
    <div>
      <h1>ユーザ登録 {user.email}</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>メールアドレス</label>
          <input name="email" type="email" placeholder="email" />
        </div>
        <div>
          <label>パスワード</label>
          <input name="password" type="password" placeholder="password" />
        </div>
        <div>
          <button>登録</button>
        </div>
      </form>
    </div>
  );
};

export default SignUp;
```

##React Routerの設定

ユーザ登録の画面の設定を行ったが認証機能を持つアプリケーションを構築する場合はユーザ登録ページだけではなくログインページやログイン完了後のページも作成する必要がある。複数のページを持つシングルページアプリケーションを構築するためにReact Routerが必要となる。

① React Routerを利用するためreact-router-domライブラリをインストールする。

```
% npm install react-router-dom
```

② インストールが完了したらRouterの設定を行っていきます。
react-router-domからBrowserRouterとRouterをimportする。
/signupにアクセスがあったらSignUpコンポーネントが表示されるように設定を行う。

```App.js
import SignUp from './components/SignUp';
import { AuthProvider } from './context/AuthContext';
//追加
import { BrowserRouter, Route } from 'react-router-dom';

function App() {
  return (
    <AuthProvider>
      <div style={{ margin: '2em' }}>
//追加
        <BrowserRouter>
          <Route path="/signup" component={SignUp} />
//追加
        </BrowserRouter>
      </div>
    </AuthProvider>
  );
}

export default App;
```

③ src/components/Home.jsとcomponents/Login.jsを作成し、編集する。

```src/components/Home.js
const Home = () => {
  return <h1>ホームページ</h1>;
};

export default Home;
```

```components/Login.js
const Login = () => {
  return <h1>ログイン画面</h1>;
};

export default Login;
```

④ App.jsに追加したコンポーネントのルーティングを追加する。
”/”にはexactを設定しますが”/”を設定しないと/signupにアクセスした場合もURLに”/”を含むのでHomeコンポーネントの内容が一緒に表示されます。

```App.js
import Home from './components/Home';
import SignUp from './components/SignUp';
import Login from './components/Login';
import { AuthProvider } from './context/AuthContext';
//追加
import { BrowserRouter, Route } from 'react-router-dom';

function App() {
  return (
    <AuthProvider>
      <div style={{ margin: '2em' }}>
//追加
        <BrowserRouter>
          <Route exact path="/" component={Home} />
          <Route path="/signup" component={SignUp} />
          <Route path="/login" component={Login} />
//追加
        </BrowserRouter>
      </div>
    </AuthProvider>
  );
}

export default App;
```

##ログインページの作成

① ユーザ登録ページの内容を元をログインページを編集する。
ユーザ登録ページではhandleSubmitメソッドでcreateUserWithEmailAndPasswordメソッドを使っていたが、ログイン時にはsignInWithEmailAndPasswordメソッドを利用する。ユーザ登録が行われていない場合は/signupに移動できるようにLinkコンポーネントをimportして設定を行なう。

```src/components/Login.js
import { auth } from '../firebase';
import { Link } from 'react-router-dom';

const Login = () => {
  const handleSubmit = (event) => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    auth.signInWithEmailAndPassword(email.value, password.value);
  };

return (
    <div>
      <h1>ログイン</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label>メールアドレス</label>
          <input name="email" type="email" placeholder="email" />
        </div>
        <div>
          <label>パスワード</label>
          <input name="password" type="password" placeholder="password" />
        </div>
        <div>
          <button>ログイン</button>
        </div>
        <div>
          ユーザ登録は<Link to={'/signup'}>こちら</Link>から
        </div>
      </form>
    </div>
  );
};

export default Login;
```

##Homeページの作成

① ホームページでログアウトができるように設定を行う。

firabase.jsからauthをimportする。サインアウトはauth.signOutメソッドで行うことができる。ログアウト後にloginページに移動できるようにReact RouterのuseHistory Hookを使う。useHistoryから作成するhistoryのpushメソッドにURLを指定するとその場所に移動することができる。

```src/components/Home.js

import { auth } from '../firebase';
import { useHistory } from 'react-router-dom';

const Home = () => {
  const history = useHistory();
  const handleLogout = () => {
    auth.signOut();
    history.push('/login');
  };
  return (
    <div>
      <h1>ホームページ</h1>
      <button onClick={handleLogout}>ログアウト</button>
    </div>
  );
};

export default Home;
```

設定後『ログアウト』を押すとloginページにリダイレクトされることを確認する。
ログイン画面からログインを行うことができるがログイン後の処理は何も設定されていないのでログインができてもブラウザには変化はない。

##アクセスの制限設定

#####1. Homeコンポーネントで分岐を利用

共有されているuserオブジェクトに対してHomeコンポーネントからアクセスを行い、userオブジェクトが値を持っていればHomeコンポーネントを表示し、持っていなければLoginコンポーネントにリダイレクトする。if文の分岐を利用しているだけ。

```src/components/Home.js

import { auth } from '../firebase';
import { useHistory, Redirect } from 'react-router-dom';
import { useAuthContext } from '../context/AuthContext';
const Home = () => {
  const history = useHistory();
  const { user } = useAuthContext();
  const handleLogout = () => {
    auth.signOut();
    history.push('/login');
  };

  if (!user) {
    return <Redirect to="/login" />;
  } else {
    return (
      <div>
        <h1>ホームページ</h1>
        <button onClick={handleLogout}>ログアウト</button>
      </div>
    );
  }
};

export default Home;
```

ユーザがログインしている状態で/(ルート)にアクセスを行う。<br/>⇨ ログインしているにも関わらず/loginにリダイレクトされる。

アクセスを行うとAuthContext.jsのuseEffectでonAuthStateChangedによってユーザがログインしているかどうかチェックが行われる。しかしonAuthStateChangedによるチェックが完了していない状態でHomeコンポーネントがマウントされるためuserには値が入っておらずリダイレクトされる。この問題を防ぐためにはuserに値が入るまでHomeコンポーネントの表示を待たせる必要がある。

#####2.AuthContextコンポーネントにloading変数を追加

デフォルト時にはloadingをtureに設定し、userに値が入った直後にloadingの値をfalseに設定する。この値がfalseになるまで表示させないように設定を行う。


```AuthContext.js
/略

export function AuthProvider({ children }) {
  const [user, setUser] = useState('');
  const [loading, setLoading] = useState(true);

  const value = {
    user,
    loading,
  };

  useEffect(() => {
    const unsubscribed = auth.onAuthStateChanged((user) => {
      setUser(user);
      setLoading(false);
    });
    return () => {
      unsubscribed();
    };
  }, []);

  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
}
```

loadingを設定後にログインした状態で/(ルート)にアクセスを行なってください。これまでに比較して表示までに時間がかかりますがログインしている場合のみホームページが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/1d7f45cd-2fae-b0c9-29c7-555486c3e2bb.png)


ログアウトボタンでログアウトしてから再度/(ルート)にアクセスを行う。
/loginにリダイレクトされる。シンプルな方法で、アクセス制限を行うことができた。

ページが開くまで少し時間がかかるのでロード中であることを表示させたい場合は以下のように設定できる。一瞬、画面には『loading…』が表示される。

```AuthContext.js
if (loading) {
  return <p>loading...</p>;
} else {
  return (
    <AuthContext.Provider value={value}>
      {!loading && children}
    </AuthContext.Provider>
  );
}
```

※次にPrivateRouteを利用した方法を説明。
PrivateRouteを設定する前にはHome.jsファイルで設定した分岐に関数するコードは削除しておいてく。

```src/components/Home.js
import { auth } from '../firebase';
import { useHistory } from 'react-router-dom';
const Home = () => {
  const history = useHistory();
  const handleLogout = () => {
    auth.signOut();
    history.push('/login');
  };

  return (
    <div>
      <h1>ホームページ</h1>
      <button onClick={handleLogout}>ログアウト</button>
    </div>
  );
};

export default Home;
```

#####3. PrivateRouteを利用した方法

Homeコンポーネントによる分岐の方法に比べて少し複雑になりますが、PrivateRouteを作成することでアクセス制限を行うことができる。Routerコンポーネントをラップする形で作成を行う(RouterコンポーネントをPrivateRouteで包むことでアクセス制限を行う)。componentsフォルダにPrivateRoute.jsファイルを作成する。

PrivateRouteコンポーネント内でも実行しているはuserオブジェクトを使った分岐である。

```src/components/PrivateRoute.js

import { Route, Redirect } from 'react-router-dom';
import { useAuthContext } from '../context/AuthContext';
const PrivateRoute = ({ component: Component, ...rest }) => {
  const { user } = useAuthContext();
  return (
    <Route
      {...rest}
      render={(routeProps) => {
        return user ? <Component {...routeProps} /> : <Redirect to="/login" />;
      }}
    />
  );
};

export default PrivateRoute;
```

…restにはPrivateRouteに設定されているpropsのpathとexactの値が入って、Component(大文字のC)には/signにアクセスすればSign, /loginであればLoginコンポーネントになりる。また、routePropsにはroute propsであるmatch, location, historyが含まれる。

App.jsファイルでRouteコンポーネントを作成したPrivateRouteに変更する。

```App.js

import Home from './components/Home';
import SignUp from './components/SignUp';
import Login from './components/Login';
import { AuthProvider } from './context/AuthContext';
import { BrowserRouter, Route } from 'react-router-dom';
//追加
import PrivateRoute from './components/PrivateRoute';

function App() {
  return (
    <AuthProvider>
      <div style={{ margin: '2em' }}>
        <BrowserRouter>
//追加
          <PrivateRoute exact path="/" component={Home} />
          <Route path="/signup" component={SignUp} />
          <Route path="/login" component={Login} />
        </BrowserRouter>
      </div>
    </AuthProvider>
  );
}

export default App;
```

ログインした状態であればホームページにアクセスでき、ログインしていない場合は/loginにリダイレクトされることを確認する。

上記のPrivateRouteコンポーネントのコードを初めてみた人は何をしているのかわからないと思うのでコードを少し変更して動作を確認する。
変更したコードではrender関数を利用せずに同様の処理を行おうがuserオブジェクトがない場合に/loginと/signupにアクセスするとRedirectにはexactの設定がないので必ずRedirectが実行される。そのため/signupでアクセスすると/loginにリダイレクトされる。

```
const PrivateRoute = ({ component: Component, ...rest }) => {
  const { user } = useAuthContext();
  return user ? (
    <Route {...rest} component={Component} />
  ) : (
    <Redirect to="/login" />
  );
};
```

render関数を利用している場合はRouteコンポーネントでexactがチェックされた後にuserの分岐が行われるため/signupにアクセスした場合にuserによる分岐処理が行われることはない。
render関数の場合もRouteコンポーネントに設定されている{…rest}を削除するとexactのチェックがないため/signupにアクセスすると/loginにリダイレクトされる。

上記のコードでもcomponentや…restに入っている値が気になる場合は下記のようにコードを書き換えることができる。

```
const PrivateRoute = ({ component, exact, path }) => {
  const { user } = useAuthContext();
  return user ? (
    <Route exact={exact} path={path} component={component} />
  ) : (
    <Redirect to="/login" />
  );
};
```

##ログインした場合のログインページへのアクセス

現在の設定ではログインしていてもユーザ登録(/signup)、ログインページ(/login)にアクセスすることができる。/(ルート)に対してはPriveateRouteを設定することでアクセス制限をしていましたがユーザ登録、ログインページについてはPublicRouteを設定することでログインしているユーザにはアクセスできないように設定を行う。

① componentsフォルダにPublicRoute.jsファイルを作成する。

```src/components/PublicRoute.js

import { Route, Redirect } from 'react-router-dom';
import { useAuthContext } from '../context/AuthContext';
const PublicRoute = ({ component: Component, ...rest }) => {
  const { user } = useAuthContext();
  return (
    <Route
      {...rest}
      render={(routeProps) => {
        return !user ? <Component {...routeProps} /> : <Redirect to="/" />;
      }}
    />
  );
};

export default PublicRoute;
```

② App.jsファイルのRouteコンポーネントをPublicRouteに変更する。

``` App.js

import Home from './components/Home';
import SignUp from './components/SignUp';
import Login from './components/Login';
import { AuthProvider } from './context/AuthContext';
import { BrowserRouter } from 'react-router-dom';
import PrivateRoute from './components/PrivateRoute';
//追加
import PublicRoute from './components/PublicRoute';

function App() {
  return (
    <AuthProvider>
      <div style={{ margin: '2em' }}>
        <BrowserRouter>
          <PrivateRoute exact path="/" component={Home} />
//追加
          <PublicRoute path="/signup" component={SignUp} />
          <PublicRoute path="/login" component={Login} />
        </BrowserRouter>
      </div>
    </AuthProvider>
  );
}

export default App;
```

ここまでの設定が終わるとログインした状態で/signupまたは/loginにアクセスすると/(ルート)にリダイレクトされる。ログインしていない状態からログインすると/loginにリダイレクトされる。

##ログイン後のリダイレクトの設定

ここまでの設定ではログイン画面でログインするとPublicRouteコンポーネントの設定で/(ルート)にリダイレクトされるがsignInWithEmailAndPasswordメソッドの後にリダイレクト処理を追加する。

① ログインが正常に行われた場合はuseHIstory Hookを利用して/(ルート)にリダイレクトできるように設定を行う。

```src/components/Login.js

import { auth } from '../firebase';
import { Link, useHistory } from 'react-router-dom';

const Login = () => {
  const history = useHistory();
  const handleSubmit = async (event) => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    await auth.signInWithEmailAndPassword(email.value, password.value);
    history.push('/');
  };
  //略
```

ログアウトを行い、ログイン画面からログインを行う。
正しいメールアドレスとパスワードを入力してログインを行った場合に/(ルート)にリダイレクトされるが、何も入れない場合や間違ったメールアドレスまたはパスワードを入れた場合はブラウザ上には何も起きない。

ブラウザ上では何も変化はないがデベロッパーツールのコンソールを見るとエラーの表示が確認できる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/e74dac09-d40c-fa58-2b75-f67707620290.png)

##ログイン失敗のエラーメッセージ

The email address is badly formatttedというエラーが表示されているのでブラウザ上にエラーメッセージが表示されるようにtry, catchとuseStateを使って設定をう。errorに値がある場合のみブラウザ上に赤文字で表示されるように設定を行う。

```src/components/Login.js

import { auth } from '../firebase';
import { Link, useHistory } from 'react-router-dom';
import { useState } from 'react';

const Login = () => {
  const history = useHistory();
  const [error, setError] = useState('');
  const handleSubmit = async (event) => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    try {
      await auth.signInWithEmailAndPassword(email.value, password.value);
      history.push('/');
    } catch (error) {
      console.log(error);
      setError(error.message);
    }
  };

  return (
    <div>
      <h1>ログイン</h1>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <form onSubmit={handleSubmit}>
  //略
```

ログインしている場合はログアウトを行い、再度誤った情報でログインを行うとブラウザ上にエラーが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/bc3e9251-b442-c1e8-de51-5bfc2e60a025.png)

##サインアップ時のリダイレクト設定

ユーザ登録（サインアップ）を行った後も/(ルート)にリダイレクトできるように設定を行い、エラーメッセージの表示もログイン画面と同様に行う。

```src/components/SignUp.js

import { auth } from '../firebase';
import { Link, useHistory } from 'react-router-dom';
import { useState } from 'react';

const SignUp = () => {
  const history = useHistory();
  const [error, setError] = useState('');
  const handleSubmit = async (event) => {
    event.preventDefault();
    const { email, password } = event.target.elements;
    try {
      await auth.createUserWithEmailAndPassword(email.value, password.value);
      history.push('/');
    } catch (error) {
      setError(error.message);
    }
  };

  return (
    <div>
      <h1>ユーザ登録</h1>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <form onSubmit={handleSubmit}>
        <div>
          <label>メールアドレス</label>
          <input name="email" type="email" placeholder="email" />
        </div>
        <div>
          <label>パスワード</label>
          <input name="password" type="password" placeholder="password" />
 </div>
        <div>
          <button>登録</button>
        </div>
        <div>
          ユーザ登録済の場合は<Link to={'/login'}>こちら</Link>から
        </div>
      </form>
    </div>
  );
};

export default SignUp;
```

##動作確認
ログインしていない状態で/(ルート)にアクセスすると/loginにリダイレクトされる。
ログインが行われると/(ルート)にリダイレクトされ、
/signupや/loginにアクセスすると/(ルート)にリダイレクトされる。
/(ルート)のページからログアウトすると/loginにリダイレクトされる。

##Googleアカウントを利用した認証
Firebaseの認証ではGoogleアカウントも利用することができる。
Googleアカウントを利用した場合の動作確認も行っておく。
Googleの行にカーソルを合わせると鉛筆のマークが表示されるのでそのままクリックする。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/f6663bdc-f8ba-1615-f5db-65d5c5821e6a.png)

有効にするを選択し、プロジェクトのサポートメールを選択すると保存が可能となるので、『保存』を押す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/74f3223a-c33c-6757-c6fc-2bfccbb28419.png)

① メールアドレスの時とは異なるメソッドを利用して接続を行うのでfirebase.jsファイルに追加を行う。

```src/firebase.js
import firebase from 'firebase/app';
import 'firebase/auth';

const firebaseConfig = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  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_SENDER_ID,
};

firebase.initializeApp(firebaseConfig);

export const provider = new firebase.auth.GoogleAuthProvider(); //追加

export const auth = firebase.auth();
```

② 追加したProviderをLogin.jsでimportして利用する。
メールアドレスを利用する場合はメールアドレスとパスワードの入力フォームが必要となり、Googleアカウントを利用する場合はログインボタンのみを利用する。

```src/components/Login.js

import { auth, provider } from '../firebase';
import { useHistory } from 'react-router-dom';
import { useState } from 'react';

const Login = () => {
  const history = useHistory();
  const [error, setError] = useState('');
  const handleLogin = async (event) => {
    try {
      await auth.signInWithPopup(provider);
      history.push('/');
    } catch (error) {
      console.log(error);
      setError(error.message);
    }
  };

  return (
    <div>
      <h1>ログイン</h1>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      <button onClick={handleLogin}>Googleログイン</button>
    </div>
  );
};

export default Login;
```
ブラウザで確認するとログイン画面にボタンのみ表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/97f3fdde-5654-f590-b9e1-8d7a9e8c9d1a.png)

ログインボタンを押すとログイン画面が表示されるのでGoogleアカウントの情報を入力してログインを行う。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/3329abf3-1c44-e73e-385e-bdf6bdbfa50d.png)

ログインが完了するとメールアドレスの時と同様にホームページにリダイレクトされ、/loginページにアクセスしてもホームページにリダイレクトされる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/8d9f3e79-1570-4f1b-6f96-8fc660a4fdef.png)

Firebaseのユーザの情報を見るとログインを行なってユーザの情報が表示されていることが確認できる。ログインを行ったアカウントを確認することができる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/2e12949f-d5eb-2b5a-ea90-3574d266af4c.png)

##参考サイト
[【完全版】ReactのFirebase Authentication(認証)を基礎からマスターする]
(https://reffect.co.jp/react/react-firebase-auth)