Udemyの『初心者がReactとfirebaseで作るモバイル対応PWAアプリケーション』という講座を学習して、
Firebaseって無料で使えるしAPIも分かり易そうだからいいんじゃね。簡単なDBならすぐ作れそうだし。FirebaseにReactでTodoアプリ作ってみよう。
と思い立ったんですね。
Authenticationの仕組みも用意されているから、自分だけじゃなくてほかの人も使えるようにサインアップやログイン/ログアウトの仕組みも組み込んで。
ということで、まずはAuthentication(認証)周りを作ったことについて記事にしました。
当記事は、アプリの仕様をざっくりと説明し、詳細なコードなどは関連記事でじっくり説明しています。
コードの解説などを知りたい方は関連記事を参照してください。
※ ソースはこちら:Github
※ アプリはこちら:「Firebaseに認証するだけのアプリ」
FirebaseにReactのアプリを作成するためにやっておくこと
FirebaseにReactのアプリを作成するためにやっておくことを書きにまとめました。
参考にしてください。
インストールしたパッケージのバージョン
インストールした主なパッケージのバージョンを示します。
作成した時点での最新パッケージを採用しています。
{
"homepage": "/portfolios/react-and-firebase-mail-and-google-auth/",
"name": "react-and-firebase-mail-and-google-auth",
"version": "0.1.0",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.13.7",
"@mui/material": "^5.13.7",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"firebase": "^9.22.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.2",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4",
"workbox-background-sync": "^6.6.0",
"workbox-broadcast-update": "^6.6.0",
"workbox-cacheable-response": "^6.6.0",
"workbox-core": "^6.6.0",
"workbox-expiration": "^6.6.0",
"workbox-google-analytics": "^6.6.0",
"workbox-navigation-preload": "^6.6.0",
"workbox-precaching": "^6.6.0",
"workbox-range-requests": "^6.6.0",
"workbox-routing": "^6.6.0",
"workbox-strategies": "^6.6.0",
"workbox-streams": "^6.6.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Authentication(認証)周りの仕様について
仕様はシンプルで、下記のような認証周りの機能を持ちます。
src配下のディレクトリ構造
src配下のディレクトリ構造を示します。
SRC
│ App.css
│ App.js
│ App.test.js
│ index.css
│ index.js
│ reportWebVitals.js
│ service-worker.js
│ serviceWorkerRegistration.js
│ setupTests.js
│
├─components
│ │ Home.js
│ │ HomeChild.js
│ │ Login.js
│ │ PrivateRoute.js
│ │ PublicRoute.js
│ │ SignUp.js
│ │ Title.js
│ │
│ └─styles
│ portal.css
│
├─context
│ AuthContext.js
│
└─service
├─authenticationProcess
│ changePassword.js
│ delUser.js
│
├─common
│ issueMsg.js
│
└─firebase
firebase.js
getCredential.js
loginFirebase.js
registration.js
sendEmailLink.js
submitPasswordResetEmail.js
App.js
まずはApp.jsの説明をします。
import React from "react";
import "./App.css";
import SignUp from "./components/SignUp";
import { AuthProvider } from "./context/AuthContext";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./components/Home";
import Login from "./components/Login";
import PrivateRoute from "./components/PrivateRoute";
import PublicRoute from "./components/PublicRoute";
import { Container } from "@mui/material";
function App() {
const URL_PASS = process.env.REACT_APP_URL_PASS;
return (
<AuthProvider>
<Container maxWidth="sm">
<BrowserRouter basename={URL_PASS}>
<Routes>
<Route
path="/"
element={
<PrivateRoute>
<Home />
</PrivateRoute>
}
/>
<Route element={<PublicRoute />}>
<Route path="/signup" element={<SignUp />} />
<Route path="/login" element={<Login />} />
<Route path="*" element={<div><h1>404 ページが見つかりません。</h1></div>} />
</Route>
</Routes>
</BrowserRouter>
</Container>
</AuthProvider>
);
}
export default App;
<AuthProvider>
プロジェクト全体で、ログインユーザーの管理をするためにContextの<AuthProvider>コンポーネントを使用しています。
<AuthProvider>のコンポーネント内で、user情報を確保し、アプリ全体で使用できるようにしています。
user情報はFirebaseから取得するという副作用になるため、useEffectを使用しています。
そして、アプリ内でuser情報を参照できるような同期をとるよう処理されています。
詳細はこちらを参照してください。
ルーティング処理
まず、Firebaseとは直接関係ないですが、ログイン等の処理で必要なルーティング処理が必要。
例を挙げると、ルートにアプリのメインの画面があるとした場合、ログインしていない状態で、ルートのURLを指定した場合には、ルートではなく、自動的にログイン画面を表示する、といった感じの処理です。
このルーティング処理を理解しておくことは、認証処理全体を理解するのに有用です。
ルーティングの仕組みは、当該アプリより簡単なコードで、こちらから解説しています。
サインアップ画面
メールアドレスとパスワードでユーザー登録(サインアップ)する画面です。
メールの形式が整っていれば、存在しないメールアドレスでの登録も可能です。
ただし、当該アプリでは、登録後のメール認証ができないとアプリの機能を使用できない仕様にしました。
存在しないメールアドレスで正常に動作させるようアプリを作成するのも可能だと思います。
Firebaseの関数
を使用して実現します。
メールアドレスとパスワードのログインのコードはこちらで解説しています。
Login画面
ログイン処理画面には以下の機能があります。
メールアドレスとパスワードでログインする処理
メールアドレスとパスワードでログインする処理。
Firebaseの関数、signInWithEmailAndPassword()を発行することによってFirebaseのアプリにログインできます。
メールリンクでログインする処理
パスワードを入力しないでも、登録したメールアドレスにログイン用のメールを送信することによってログインすることもできるようにしました。
Firebaseの関数、sendSignInLinkToEmail()とsignInWithEmailLink()を発行することによってFirebaseのアプリにログインできます。
パスワードを忘れてしまった時の処理
パスワードを忘れてしまった時の対処は必須ですね。
メールアドレスを指定することによって、ユーザーにパスワード変更するためのリンクを貼ったメールを送信します。
そうすると、パスワードリセットをするためのURLが送られてくるので、それをクリックして、
ここに新しいパスワードを入力することによって、新しいパスワードがFirebaseにセットされます。
そうすると、新しいパスワードでログインが可能になります。
sendPasswordResetEmail()を発行することによって、パスワードのリセットを行います。
Google IDでログインするときの処理
Google IDで当該アプリにログインすることもできます。
FirebaseがGoogle IDでログインしてくれる画面を出してくれます。
その画面から、ログインするGoogle IDをクリックすることによって、当該アプリにログインすることができます。
ログインに関してのコードは、こちらから解説しています。
ホーム(メイン)
アプリのメイン画面。
本記事で紹介しているアプリは、Firebaseのアプリにログインやサインアップをするだけのアプリなので、メインの機能はログインしているユーザのメールアドレスと、プロバイダを出力するだけです。
それから、ホーム(メイン)の画面から、
の処理ができるようにしています。
ホーム(メイン)画面
ルートディレクトリの示す画面です。
ユーザのメールアドレスと、使用しているプロバイダだけを出力します。
メールアドレスとパスワードで登録している場合は、「email Login」。
Google IDで登録している場合は、「Google」と出力します。
Home.js:ホーム(メイン)画面のモジュール
// Home.js
import { signOut } from "firebase/auth";
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import { createPortal } from "react-dom";
import { HomePasswordChangeModal, HomeWithdrawalChild } from "./HomeChild";
import { auth } from "../service/firebase/firebase";
import { useAuthContext } from "../context/AuthContext";
import Title from "./Title";
import { Button } from "@mui/material";
import { Logout } from "@mui/icons-material";
import PersonRemoveIcon from "@mui/icons-material/PersonRemove";
import PublishedWithChangesIcon from '@mui/icons-material/PublishedWithChanges';
const ModalPortal = ({ children }) => {
const target = document.querySelector(".modalContainer");
return createPortal(children, target);
};
const Home = () => {
const [withdrawalModalOpen, setWithdrawalModalOpen] = useState(false);
const [passwordChangeModalOpen, setPasswordChangeModalOpen] = useState(false);
const navigate = useNavigate();
const { user } = useAuthContext();
const handleLogout = () => {
signOut(auth);
navigate("/login");
};
return (
<>
<div>
<Title />
<div className="modalContainer"></div>
<p>現在ログインしているユーザーの情報</p>
<p>email : {user.email}</p>
<p>
Provider :
{user.providerData[0].providerId === "password"
? "email Login"
: user.providerData[0].providerId === "google.com"
? "Google"
: "invalid provider"}
</p>
<p>
{!user.emailVerified &&
"アドレス未認証のためTodoは入力できません。(メール認証が終了したら、リロードしてください。)"}
</p>
<Button
onClick={handleLogout}
variant="outlined"
startIcon={<Logout />}
sx={{ mr:1 }}
>
ログアウト
</Button>
<Button
onClick={() => setWithdrawalModalOpen(true)}
variant="outlined"
startIcon={<PersonRemoveIcon />}
sx={{ mr:1 }}
>
退会
</Button>
{user.providerData[0].providerId === "password"
&& <Button onClick={() => setPasswordChangeModalOpen(true)}
variant="outlined"
startIcon={<PublishedWithChangesIcon />}
>
パスワード変更
</Button> }
</div>
{withdrawalModalOpen && (
<ModalPortal>
<HomeWithdrawalChild
setWithdrawalModalOpen={setWithdrawalModalOpen}
/>
</ModalPortal>
)}
{passwordChangeModalOpen && (
<ModalPortal>
<HomePasswordChangeModal
setPasswordChangeModalOpen={setPasswordChangeModalOpen}
/>
</ModalPortal>
)}
</>
);
};
export default Home;
ユーザー情報の出力
// Home.jsの抜粋
~
<div className="modalContainer"></div>
<p>現在ログインしているユーザーの情報</p>
<p>email : {user.email}</p>
<p>
Provider :
{user.providerData[0].providerId === "password"
? "email Login"
: user.providerData[0].providerId === "google.com"
? "Google"
: "invalid provider"}
</p>
<p>
{!user.emailVerified &&
"アドレス未認証のためTodoは入力できません。(メール認証が終了したら、リロードしてください。)"}
</p>
~
『Firebaseに認証するだけのアプリ』には、
しか表示する機能がありません。
もしこのコードを利用してFirebaseのアプリを作るときには、ここにDBから持ってきた情報などを表示するような作りこみをすればいいわけです。
user.providerData[0].providerId:ログインしているユーザのプロバイダ名
プロバイダー情報を抽出しています。
user.providerData[0].providerIdしか参照していません。
Google IDと同じメールアドレスで登録すると、複数のプロバイダーで登録してあることになりuser.providerDataの配列を検索していかなくてはいけないと思います。
当該アプリの要修正ポイントです。
user.emailVerified:サインアップに対して、メール承認が完了しているか
メールアドレスとパスワードで登録した時、そのメールアドレスにメールを送信して、そのメールのリンクをクリックすることによって、user.emailVerifiedがtrueになります。
いい加減なアドレス登録の場合に、アプリの機能を使用不能にしたいときには、user.emailVerifiedを使います。
モーダル処理
「退会」「パスワード変更」ボタンをクリックした時、確認メッセージをモーダルで出力します。
今回はFirebaseのアプリにログインやサインアップする処理を解説する記事なので、モーダルに関しての解説は割愛します。
ログアウト処理
// Home.jsの抜粋
~
const handleLogout = () => {
signOut(auth);
navigate("/login");
};
~
ログアウトボタンをクリックすると、当該ハンドラに渡ってきて、signOut(auth)を発行することによってログアウトします。
これは、メールアドレスとパスワードで登録したユーザーでも、Google IDで登録したユーザーでも同じです。
ログアウトが成功したなら、”~/login”に制御を渡します。
退会処理
退会処理は、Firebaseに登録したメールアドレスやGoogle IDを削除します。
メールアドレス&パスワードで登録した時と、Google IDで登録した時で処理が異なります。
メールアドレス&パスワードで登録した時の退会処理
退会処理のモーダル画面で「本当に退会する」をクリックしたときに、パスワードを聞いてくるウインドウが出力するので、それにパスワードを入力することで、メールアドレス&パスワードで登録したユーザーの退会ができます。
処理の詳細はこちらから解説しています。
Google IDで登録した時の退会処理
Google IDでログインしているときには、パスワードの変更は当該アプリでは行わないので、ボタンを表示していません。
「退会」ボタンをクリックすると、退会の確認画面が出力。
Google IDで登録を退会するために。再ログインが必要なので、同じGoogle IDでログイン操作をすることを指示するメッセージを出力。
「OK」ボタンをクリックすると、Google IDによるログイン画面がポップアップするので、
メッセージで示したように、現在ログインしているGoogle IDで再ログインをすると。
退会処理が完了します。
メールアドレスとパスワードでユーザー登録したユーザーの退会処理と同じように、reauthenticateWithCredential()を使用して再認証しようと思いましたが、Firebaseのマニュアル等を参考にしていろいろ試しましたが、上手くいきませんでした。
そこで、再ログインという方法をとってユーザーの退会処理を行いました。
Google IDで登録した時の退会処理の詳細はこちらから解説しています。
パスワード変更処理
アプリに設定したパスワードを忘れてしまうというのはよくあること。
アプリに、パスワードを忘れたときの処理は必須です。
Firebaseの認証処理にもパスワードを忘れたときに使う関数が用意されています。
ホーム画面
当該アプリでは、ホーム画面の「パスワード変更」ボタンをクリックすることで、パスワードの変更を行えます。
Google IDでユーザー登録したユーザーのホーム画面には、「パスワード変更」のボタンは出現しません。
新パスワード入力画面
今までのパスワードと新パスワード(2回)を入力して「パスワードの変更」ボタンをクリック。
正しいパスワードを入力すれば、パスワードの変更が完了します。
パスワードの変更の処理の詳細はこちらから解説しています。
最後に
アプリの不具合点について
Google IDと同じメールアドレスでサインアップした場合
Google IDと同じメールアドレスでサインアップした場合は、Google IDでもメールアドレスでもログイン可能です。
ただし、メイン画面の「Provider」では、どちらか一つのプロバイダーしか表示されません。
退会処理をすると、Google IDとメールアドレスどちらも登録抹消されます。
このブログに掲載されているコードは、著作権者(私)に帰属します。コードを自由に使用、複製、改変、配布、販売、サブライセンスできます。また、コードを使用した結果、何らかの損害が発生した場合でも、著作権者は一切責任を負いません。