React, Firebase 使った Googleアカウント連携したログインをやってみる

React, Firebase を使って Googleアカウント使ってのログイン機能をつくってみる
Google アカウント使ったログインに関する処理は一つのモジュール `AuthContext.js` にまとめてみました
`useContext` 使って全体でステータス管理できるようにしてる感じです
実施した環境とざっくり手順はこんなかんじ

- 実施した環境
  - Windows11
  - node v18.17.1
  - React v18.2.0
  - firebase v10.7.1
- ざっくり手順
  - Firebase ログイン・アプリ利用準備
  - React から Firebase 使ったログイン機能作成

あと完成したやつは github においてます（Firebase の定義情報は各自の値で更新が必要）
https://github.com/sueasen/my-app-googleauth/tree/main

# Firebase ログイン・アプリ利用準備
1. Firebase のサイトにアクセス
    https://firebase.google.com/
1. [使ってみる] をクリック → Google ログイン(ログイン入力画面は割愛)
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/bafe64ff-6dce-a3c1-414f-e2fa9c019d56.png)
1. [プロジェクトを作成] をクリック
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/cdd7a55a-16fd-6f84-95ec-fb1c23179252.png)
1. プロジェクト情報を以下のように設定しプロジェクト作成 → 準備できたら続行
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/7c358691-688b-df73-3fcb-f2a708f28287.png)
1. [Authentication] をクリック → 始める
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/8e519336-bfd5-a5bc-3e1a-181fe4364ebe.png)
1. 追加のプロバイダを Google にして以下のように設定して [保存] をクリック
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/389e6740-7b60-afbb-70ac-a33018fcd5ca.png)
1. Firebase をアプリ利用追加の [ウェブアイコン] をクリック
    ※Firebase SDK の追加にある firebaseConfig はReactから使う情報になります
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/1d50c514-6e1d-6723-2ee4-12f0e762287a.png)


# React から Firebase 使ったログイン機能作成
1. プロジェクトを作成する

    :::note info
    こちらを参考に実施（以降は参考にしたやつをベースに進めます）
    https://qiita.com/sueasen/items/fe63a1693b2057b29d84


1. Firebase, heroicons をインストール
    ```powershell
    npm i firebase @heroicons/react
    ```
1. src 直下に contexts ディレクトリ作成

    :::note info
    ディレクトリ作るの忘れると以降の import でパスがずれます
    
1. src/contexts 直下に AuthContext.js 作成

    :::note warn
    firebaseConfig の定義情報 (13行目～) は Firebase のアプリ利用で取得した値を使用
    設定する値は Firebase にログインしてプロジェクトの設定から確認できます
    

```javascript:AuthContext.js
import { initializeApp } from 'firebase/app';
import {
  getAuth,
  GoogleAuthProvider,
  signInWithPopup,
  signOut,
} from 'firebase/auth';
import { useEffect, useState } from 'react';
import { createContext, useContext } from 'react';

// Context生成(ログインに関する情報を管理)
const AuthContext = createContext();
// firebase の定義情報（各値はFirebaseのアプリ利用で取得した値を使用する）
const firebaseConfig = {
  apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  authDomain: 'xxxxxxxxxx.firebaseapp.com',
  projectId: 'xxxxxxxxxx',
  storageBucket: 'xxxxxxxxxx.appspot.com',
  messagingSenderId: '888888888888',
  appId: '1:888888888888:web:xxxxxxxxxxxxxxxxxxxxxxxxx',
};

// firebase, GoogleAuth 初期設定
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const provider = new GoogleAuthProvider();

// AuthContextProvider (Provider)
export const AuthContextProvider = ({ children }) => {
  // ログインユーザ
  const [loginUser, setLoginUser] = useState();

  // 起動時ログイン処理(既にログインしてる場合, ユーザ設定)
  useEffect(() => {
    // auth 初期化時にログインユーザ設定
    auth.onAuthStateChanged((user) => setLoginUser(user));
  }, []);

  // ログイン処理
  const login = async () => {
    // Google ログインのポップアップ表示して認証結果取得
    const result = await signInWithPopup(auth, provider);
    // 認証結果より user 設定
    setLoginUser(result.user);
  };

  // ログアウト処理
  const logout = async () => {
    await signOut(auth);
    setLoginUser(null);
  };

  // ログイン情報設定したProvider
  return (
    <AuthContext.Provider
      value={{
        loginUser,
        login,
        logout,
      }}
    >
      {children}
    </AuthContext.Provider>
  );
};

// AuthContextConsumer (useContext) # Provider で囲った範囲で使う必要あり
export const AuthContextConsumer = () => {
  return useContext(AuthContext);
};

```

5．src 直下の App.js を修正
- AuthContextProvider の import 追加
- コンポーネント返してるところの全体を AuthContextProvider で括る

    ```javascript:App.js
    import { appRouter } from './pages/AppRouter';
    import { AuthContextProvider } from './contexts/AuthContext';
    import Home from './pages/Home';
    import Page1 from './pages/Page1';
    import Page2 from './pages/Page2';
    import './App.css';
    
    // ページ情報を定義して appRouter に設定
    const pages = [
      { key: 'Home', path: '/', element: <Home /> },
      { key: 'Page1', path: '/page1', element: <Page1 /> },
      { key: 'Page2', path: '/page2', element: <Page2 text="text sample" /> },
    ];
    const router = appRouter(pages);
    
    const App = () => {
      return (
        <AuthContextProvider>
          {router.navbarLink}
          {/* 位置調整で main で括る */}
          <main>{router.browserRouter}</main>
        </AuthContextProvider>
      );
    };
    
    export default App;
    ```

6．src/pages 直下に LoginUser.js を作成

```javascript:LoginUser.js
import { UserCircleIcon } from '@heroicons/react/24/solid';
import { AuthContextConsumer } from '../contexts/AuthContext';

const LoginUser = () => {
  // AuthContextConsumer からログインユーザ、ログイン・ログアウト処理取得
  const { loginUser, login, logout } = AuthContextConsumer();
  return (
    <>
      <div className="user_info">
        <UserCircleIcon className="user_icon" />
        <p className="user_name">
          {loginUser ? loginUser.displayName : 'ゲスト'}
        </p>
        <button className="login_btn" onClick={loginUser ? logout : login}>
          {loginUser ? 'logout' : 'login'}
        </button>
      </div>
    </>
  );
};
export default LoginUser;
```

7．src 直下の App.css を修正
- LoginUser で設定したクラスに対してのスタイルを追記

    ```css:App.css
    .user_info {
        position: fixed;
        right: 0;
        top: 0;
        height: var(--height-navbar);
        display: flex;
        align-items: center;
        color: #ddd;
        font-size: 14px;
        letter-spacing: 1px;
    
        & .user_icon {
            height: calc(var(--height-navbar) - 10px);
        }
    
        & .user_name {
            margin: 0 5px;
            width: 80px;
        }
    
        & .login_btn {
            height: calc(var(--height-navbar) - 20px);
            width: 80px;
            margin: 0 5px;
            border-radius: 25px;
            color: #ddd;
            background: #1e1d1c;
            text-transform: uppercase;
        }
    
        & .login_btn:hover {
            opacity: 0.5;
        }
    }
    ```

8．src/pages 直下の AppRouter.js を修正
- LoginUser のインポート追加
- links (ヘッダーメニュー部) の最後に LoginUser を追加

```javascript:AppRouter.js
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import LoginUser from './LoginUser';

export const appRouter = (pages) => {
  const router = () => {
    return (
      <>
        <BrowserRouter basename={process.env.PUBLIC_URL}>
          <Routes>
            {pages.map((p) => (
              <Route key={p.key} path={p.path} element={p.element} />
            ))}
          </Routes>
        </BrowserRouter>
      </>
    );
  };
  // タグ, クラスなど追加
  const links = () => (
    <header className="header">
      <div className="navtext-container">
        <div className="navtext">title</div>
      </div>
      <input type="checkbox" className="menu-btn" id="menu-btn" />
      <label htmlFor="menu-btn" className="menu-icon">
        <span className="navicon"></span>
      </label>
      <ul className="menu">
        {pages.map((p) => (
          <li key={p.key}>
            <a href={`${process.env.PUBLIC_URL}${p.path}`}>{p.key}</a>
          </li>
        ))}
      </ul>
      <LoginUser />
    </header>
  );
  return {
    browserRouter: router(),
    navbarLink: links(),
  };
};
```

9．実行確認

起動して [ログイン] クリック → Google認証 → ログイン成功(ユーザ名表示)でOK
※Google認証のポップアップ出てこない時はポップアップブロックを解除

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51758/261c1916-8f4b-cd32-7012-b050cefeb199.png)