Firebase Authenticationで認証機能を追加してみた

作成日:

react
firebase

やりたいこと

サイトにアクセスした際のログイン画面、サインアップ画面と認証機能を作りたい。そして、認証に成功したユーザだけ Top 画面に遷移できるようにする。

概要

今回は、react で作成したアプリにログイン画面と認証機能を追加するまでを行いたいと思います。また、今回利用するログイン画面などのソースコードはこちらのコードを参照してください。

使用技術

  • Firebase Authentiation
  • React

Firebase Authentication とは

Google が提供しているユーザ情報を管理し、ユーザ認証機能が簡単に利用できるサービスです。ドキュメントには以下のように書かれています。

Firebase Authentication には、バックエンド サービス、使いやすい SDK、アプリでのユーザー認証に使用できる UI ライブラリが用意されています。Firebase Authentication では、パスワード、電話番号、一般的なフェデレーション ID プロバイダ(Google、Facebook、Twitter)などを使用した認証を行うことができます。

Firebase の準備

  1. まず Firabase でプロジェクトを作成し、ウェブアプリを追加してください。プロジェクトの作成とアプリの追加方法はこちらのリンクの「1.Firebase プロジェクトの設定」を参考にしてください。
  2. Firebase のプロジェクトが作成できたら、Authentication の画面で Sign-in method のタブでログイン方法を追加します。今回は、「メール/パスワード」を利用します。
    firebase_authnetication1_format.png
  3. 「メール/パスワード」を選択したら、「メール/パスワード」を有効にするをオンにして保存します。これで、メールアドレスとパスワードでのログインができるようになりました。 firebase_authentication2.png
  4. ツールバーのプロジェクトの設定ページで apiKey、authDomation、projectId、storageBucket、messagingSnederId を記録してく置いてください。後の設定で使います。
    firebase_authentication3_format.png

アプリの作成

  1. まず下記のコマンドで react のアプリケーションの雛形を作成します。
$ npx create-react-app firebase-authentication-app  //firebase-authenticationa-appの箇所は自由に変更してください
  1. そのほかに今回利用するライブラリをインストールします。
$ yarn add firebase                     //^9.6.6
$ yarn add react-router-dom      //^6.2.1
$ yarn add styled-components  //^5.3.3
  1. まずプロジェクト直下に.env ファイルを作成します。そして、Firebase の準備の 4.で記録しておいた値を入力してください。上から、apiKey、authDomation、projectId、storageBucket、messagingSnederId の順番です。
REACT_APP_FIREBASE_KEY="~~~~~~~~~~"
REACT_APP_FIREBASE_DOMAIN="~~~~~~~~~~"
REACT_APP_FIREBASE_PROJECT_ID="~~~~~"
REACT_APP_FIREBASE_STORAGE_BUCKET="~~~~~~~~~~~"
REACT_APP_FIREBASE_SENDER_ID="~~~~~~~~~~~"
  1. 次に、src 直下に、firebase.js を作成してください。

firebase.js


import firebase from "firebase/compat/app";
import "firebase/compat/auth";

firebase.initializeApp({
  apiKey: process.env.REACT_APP_FIREBASE_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID
})

const firebaseAuth = firebase.auth();

export { firebaseAuth };
  1. src 直下に auth ディレクトリを作成してください。
  2. src/auth/useFirebaseAuth.js を作成してください。

useFirebaseAuth.js


import { useState, useCallback, useEffect } from "react";
import { firebaseAuth } from "../firebase";

const useFirebaseAuth = () => {
  const [currentUser, setCurrentUser] = useState(firebaseAuth.currentUser);
  const [isAuthenticated, setIsAuthenticated] = useState(false);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    let unmounted = false;
    firebaseAuth.onAuthStateChanged((user) => {
      if (user) {
        user.getIdTokenResult(true).then((result) => {
          if (!unmounted) {
            setIsAuthenticated(true);
            setCurrentUser(user);
            setIsLoading(false);
          }
        });
      } else if (!unmounted) {
        setIsAuthenticated(false);
        setCurrentUser("");
        setIsLoading(false);
      }
    });

    return () => {
      unmounted = true;
    };
  }, []);

  const cleanUpAuthentication = () => {
    setIsAuthenticated(false);
    setCurrentUser("");
    setIsLoading(true);
  };

  const signup = useCallback((email, password) => {
    firebaseAuth
      .createUserWithEmailAndPassword(email, password)
      .catch((err) => alert(err));
  }, []);

  const login = useCallback((email, password) => {
    firebaseAuth
      .signInWithEmailAndPassword(email, password)
      .catch((err) => alert(err));
  }, []);

  const logout = useCallback(() => {
    firebaseAuth.signOut();
    cleanUpAuthentication();
  }, []);

  return {
    signup,
    login,
    logout,
    currentUser,
    isAuthenticated,
    isLoading,
  };
};

export default useFirebaseAuth;

  1. src/auth/Login.js を作成してください。これは、ログインを行う画面を作成しています。

Login.js


import { useState } from "react";
import { Navigate, Link } from "react-router-dom";
import styled from "styled-components";
import useFirebaseAuth from "./useFirebaseAuth";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const { login, isAuthenticated } = useFirebaseAuth();
  const isInvalid = password === "" || email === "";
  if (isAuthenticated) {
    return <Navigate to="/" />;
  }

  return (
    <Body>
      <Title>ログイン</Title>
      <Email>
        Email:
        <input
          value={email}
          type="email"
          onChange={(e) => setEmail(e.target.value)}
        />
      </Email>
      <Password>
        Password:
        <input
          value={password}
          type="password"
          onChange={(e) => setPassword(e.target.value)}
        />
      </Password>
      <LoginButton
        disabled={isInvalid}
        type="submit"
        onClick={() => login(email, password)}
      >
        ログイン
      </LoginButton>
      <HorizontalLine />
      <p>アカウントがない方はこちら</p>
      <Link to="/signup">
        <TransitionCreateAccount data-testid="signup-button">
          アカウント作成はこちら
        </TransitionCreateAccount>
      </Link>
    </Body>
  );
};

const Body = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 20px;
`;
const Title = styled.div`
  font-weight: bold;
  font-size: 30px;
`;
const Email = styled.div`
  font-size: 24px;
  margin: 40px 0 0 60px;
  input {
    font-size: 24px;
    height: 43px;
    width: 200px;
  }
`;
const Password = styled.div`
  font-size: 24px;
  margin: 20px;
  input {
    font-size: 24px;
    height: 43px;
    width: 193px;
  }
`;
const LoginButton = styled.button`
  color: #3366ff;
  font-size: 24px;
  width: 340px;
  height: 56px;
  background: #faf9f9;
  border: solid 1px #3366ff;
  border-radius: 4px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
  margin: 32px;
`;
const HorizontalLine = styled.hr`
  width: 100%;
`;
const TransitionCreateAccount = styled.button`
  color: #faf9f9;
  font-size: 24px;
  width: 340px;
  height: 56px;
  background: #3366ff;
  border: solid 1px #0f9ada;
  border-radius: 4px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
  margin: 20px;
  :active {
    border: solid 1px #03a9f4;
    box-shadow: none;
    text-shadow: none;
  }
`;

export default Login;
  1. src/auth/SignUp.js を作成してください。これは、サインアップ行う画面を作成しています。

SignUp.js


import { useState } from "react";
import { Navigate, Link } from "react-router-dom";
import styled from "styled-components";
import useFirebaseAuth from "./useFirebaseAuth";

const SignUp = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const { signup, isAuthenticated } = useFirebaseAuth();

  if (isAuthenticated) {
    return <Navigate to="/" />;
  }
  return (
    <Body>
      <Title>アカウント作成</Title>
      <Email>
        Email:
        <input
          value={email}
          type="email"
          onChange={(e) => setEmail(e.target.value)}
        />
      </Email>
      <Password>
        Password:
        <input
          value={password}
          type="password"
          onChange={(e) => setPassword(e.target.value)}
        />
      </Password>
      <SubmitButton onClick={() => signup(email, password)}>
        アカウント作成
      </SubmitButton>
      <HorizontalLine />
      <p>すでにアカウントがある方はこちら</p>
      <Link to="/login">
        <TransitionCreateAccount data-testid="signup-button">
          ログインはこちら
        </TransitionCreateAccount>
      </Link>
    </Body>
  );
};

const Body = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 20px;
`;
const Title = styled.div`
  font-weight: bold;
  font-size: 30px;
`;
const Email = styled.div`
  font-size: 24px;
  margin: 40px 0 0 60px;
  input {
    font-size: 24px;
    height: 43px;
    width: 200px;
  }
`;
const HorizontalLine = styled.hr`
  width: 100%;
`;
const Password = styled.div`
  font-size: 24px;
  margin: 20px;
  input {
    font-size: 24px;
    height: 43px;
    width: 200px;
  }
`;
const SubmitButton = styled.button`
  color: #3366ff;
  font-size: 24px;
  width: 340px;
  height: 56px;
  background: #faf9f9;
  border: solid 1px #3366ff;
  border-radius: 4px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
  margin: 20px;
`;
const TransitionCreateAccount = styled.button`
  color: #faf9f9;
  font-size: 24px;
  width: 340px;
  height: 56px;
  background: #3366ff;
  border: solid 1px #0f9ada;
  border-radius: 4px;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
  margin: 20px;
`;

export default SignUp;
  1. src/Top.js を作成してください。これは、ログインが成功した後に表示させる画面です。

Top.js


import useFirebaseAuth from "./auth/useFirebaseAuth";

const Top = () => {
  const { logout } = useFirebaseAuth();
  return (
    <>
      <h1>ログイン完了</h1>
      <button onClick={() => logout()}>ログアウト</button>
    </>
  );
};
export default Top;
  1. src/App.js を下記のコードに修正してください。

App.js


import { Route, Navigate, Routes } from "react-router-dom";
import useFirebaseAuth from "./auth/useFirebaseAuth";
import Top from "./Top.js";
import Login from "./auth/Login";
import SignUp from "./auth/SignUp";

function App() {
  const { isAuthenticated, isLoading } = useFirebaseAuth();
  if (isLoading) return <p>loading...</p>;
  return (
    <>
      <Routes>
        <Route
          path="/"
          element={isAuthenticated ? <Top /> : <Navigate to="/login" />}
        />
        <Route exact path="/login" element={<Login />} />
        <Route exact path="/signup" element={<SignUp />} />
      </Routes>
    </>
  );
}
export default App;
  1. src/index.js を下記のコードに修正してください。

index.js


import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>,
  document.getElementById("root")
);

動作確認

  1. まずアプリを起動してください。
$ yarn start
  1. 次に、http://localhost:3000/ にアクセスしてください。まだログインしていないので http://localhost:3000/login にリダイレクトされると思います。
    firebase_authentication4.png

  2. まだアカウントを作成していないので、「アカウント作成はこちら」からアカウントを作成しましよう。自由にメールアドレスと パスワードを入力してください。(例  Emai:tete@stst.com、Password:adadaminmin1234)

  3. 「アカウントを作成」をクリックすると、アカウントが作られログイン状態になり Top 画面に遷移します。 firebase_authentication5.png

  4. ログアウトボタンを押すとログイン画面に遷移します。

  5. そして、先ほど登録したメールアドレスとパスワードを入力し、「ログイン」を押すと Top 画面に遷移できます。

これで一通りの機能の確認ができました。もし、どこかでエラーが発生してうまく動作しなかったら、package.json から firebase のバージョンを確認してみてください。今回私は、^9.6.6 を利用してのですが^9 以降に仕様が変わっている場所があります。

おわりに

今回は、Firebase Authentiation を利用してユーザ認証機能をアプリに追加しました。このサービスを利用することで簡単に認証機能を実装することができました。また、今回紹介させていただいたコードは他のコードに埋め込みやすいように書いたつもりなので、ぜひご自身のコードに組み込んでください。

参考資料


By kiyo