メールとパスワードによるユーザー認証の実装方法 – Firebase + React

Firebase上のAuthenticationには様々な方法が提供されていますが、本稿では比較的簡単なメールとパスワードによるユーザー認証(サインアップ・ログイン)の方法をご紹介します。ユーザー認証にFirebaseを使うメリットとしては、(1)バックエンドサーバとdbの構築が不要、(2)React側で特別なグローバルな状態管理をせずともユーザーのログイン状態をグローバルに保持できる、(3)ユーザーのパスワードの管理が不要(管理者でも見れない)、の3つがあります。

本稿での設定の前提として、1.Firebaseプロジェクト内でアプリの登録があることと、2.Firebase上でAutheticationの設定が済んでいることの2点が必要です。前段については私のこちらの記事「FirebaseでWebアプリをデプロイする方法」の1章「Firebaseのサイト上での初期設定」と8章「Firebaseプロジェクト内でのアプリの登録方法」をご覧ください(2章-7章はスキップ)。後者については私のこちらの記事「React + Firebaseを使ったユーザー認証の実装方法」の2章「Authenticationの設定」を参考に、emailとパスワードによる認証を有効にしてください。

Firebaseのconfigファイル

前述の私の別の記事にも記載しましたが、使用するconfigファイルは次のようなものです。こちらのファイルをReactプロジェクトのApp.jsと同じ階層に配置します。ファイル名はfirebase.js、またはfirebase.config.jsとすることが一般的です。

firebase.js

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

const firebaseConfig = {
apiKey: "adfasfavnaAJFDKCnjkasjlkASJLFNKSA",
authDomain: "myapp-d92bc.firebaseapp.com",
projectId: "myapp-d92bc",
storageBucket: "myapp-d92bc.appspot.com",
messagingSenderId: "15645156456156",
appId: "1:15645156456156:web:1516532e652c5459f5f6b44",
};

const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);

export { auth };
export default db;

configファイルの始めの3行で必要なライブラリーをインストールします。initializeAppは文字通りの役割です。2行目のgetAuthは、各ログインユーザーのAuth(認証情報)ではなく、クライアント側からfirebaseのAuthenticationサーバーと接続に使うAuthオブジュトを取得するための関数です。getAuth関数はuseEffectの内側に入れずとも、コンポーネントがロードされた時点でauthを取得します。反対にuseEffectの内側では機能しません。

3行目のgetFirestoreはfirestoreとの接続に使用します。本稿でご紹介するユーザー認証にfirestoreは不要ですが、実際のアプリケーションではユーザー認証とfirestoreをセットで使うことが多いです。firebaseConfigオブジェクトの内側は、firebaseでアプリを登録したときに表示される値を貼り付けてください(本稿の値は架空のものです)。

下段のinitializeApp()関数にて、firebaseConfigを引数としてappオブジェクトを取得します。続く2行では、それぞれgetAuthとgetFirestoreのオブジェクトをauthとdbに格納します。公式ドキュメンテーションではconfigファイルでgetAuth()を呼び出さず、各コンポーネントで関連する関数を呼び出す都度、getAuth()を呼び出しており、この方法でも問題なく動きます。また、appをgetAuthの引数とせずとも問題なく動きます。

最後の2行でauthとdbをエクスポートしています。いずれがdefault exportでも問題ありません。

必要なライブラリーのインストール

続いて必要なライブラリーをnpm installします。1行目はfirebaseに不可欠です。2行目のreact-router-domは複数ページを作成するためのライブラリーです。ユーザー認証とは直接関係ないですが、本稿の事例ではユーザーのログイン後に画面を切り替える(リダイレクトする)ために使用します。3行目のfirebase-toolsはグローバルにインストールし、firebaseでのホスティングに使用します。グローバルなので一度インストールすれば不要、かつ、firebaseでホスティングしない場合は不要です。

npm install firebase
npm install react-router-dom
npm install -g firebase-tools

以下、本稿では下記の順番で解説いたします。


Firebase Authのサインアップ、ログアウト、サインイン

サインアップの実装方法

サインアップ(ユーザーによる初回登録)のためのコンポーネントは次の通りです。ユーザーはユーザー名、email、パスワードを入力し、最後にサインアップのボタンを押すことでfirebaseに情報が保存されます。以下順に解説いたします。

SignUp.jsx

import { useState } from "react";
import { useNavigate } from "react-router-dom";
import {
updateProfile,
createUserWithEmailAndPassword,
} from "firebase/auth";
import { auth } from "../firebase.config";

function SignUp() {
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
});
const { name, email, password } = formData;
const navigate = useNavigate();

const onChange = (e) => {
setFormData({
...formData,
[e.target.id]: e.target.value,
});
};

const onSubmit = async (e) => {
e.preventDefault();
try {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
updateProfile(auth.currentUser, {
displayName: name,
});
navigate("/");
} catch (error) {
console.log(error);
}
};

return (
<div>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Name"
id="name"
value={name}
required
onChange={onChange}
/>
<input
type="email"
placeholder="Email"
id="email"
value={email}
required
onChange={onChange}
/>
<input
type="password"
placeholder="Password"
id="password"
value={password}
required
onChange={onChange}
/>
<button type="submit">
Submit
</button>
</form>
</div>
);
}

export default SignUp;

useState

Firebaseにユーザー名、email、パスワードをアップロードする前提として、ローカルステート(ローカルの状態)にユーザーからの入力を保存します。それぞれに3本のuseStateを用意してもいいのですが、本事例では1つのuseStateに1つのオブジェクトとして3つの値を保持する方法をとっています。それぞれの初期値は空のテキストです。

const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
});

1つのformDataオブジェクトから、name、email、passwordそれぞれの値を取り出して保存します。destructure(分割代入)と呼ばれます。

const { name, email, password } = formData;

useNavigate

ユーザー登録(サインアップ)が完了してログイン後、画面を遷移(リダイレクト)させるためにuseNavigateを使用します。useNavigateの詳細他、React routerの使い方については私のこちらの記事「Reactで複数のページを作る方法 – Router v6.0準拠」をご覧ください。

const navigate = useNavigate();

onChange

ユーザーからの入力をローカルステートであるuseStateに保存するためのonChange関数を定義します。onChangeはinputタグの属性名ですが、関数名と一致させることが一般的です。inputタグの現在の状態が保存されるeを引数とします。setFormDataは先ほど定義したuseStateの状態を更新させる関数です。formDataはオブジェクトなので、引数もオブジェクトとします。ドットが3つ続く…formData部分で、現在の状態をそのままコピーします。その上で次の行で現在入力中の要素(例えばユーザー名)の部分だけを上書きします。各inputタグのidには、formDataのキー名と同じname、email、passwordを付したので、[e.target.id]とすることid名、すなわちformDataのキー名を指定して、e.target.valueの部分でユーザーの入力をローカルステートに保存・更新できます。なおuseStateを1つにまとめず、3つに分割した場合は、onChangeもそれぞれに対応するために3本必要です。onChangeの注意すべき挙動については私のこちらの記事「FormでのuseRefの使い方」もご覧ください。

const onChange = (e) => {
setFormData({
...formData,
[e.target.id]: e.target.value,
});
};

onSubmit

formタグの属性としてonSubmitがあります。同じ名前の関数を定義し、ユーザーがボタンを押したときの処理を記述します。まず、firebaseへのアップロードの部分で非同期処理が発生するのでonSubmit全体をasync関数とします。e.preventDefault();の部分は、ユーザーがsubmitしたときの画面のリフレッシュを防ぐためのコードです。

firebaseへの登録が失敗する可能性も考慮して、try・catchの構造をとります。tryの内側がfirebaseにサインアップに係る情報(emailとpassword)をアップロードする部分です。

createUserWithEmailAndPasswordというのは本コンポーネントの上段でインポートしたfirebaseの関数です。第一引数のauthはfirebaseのconfigファイルで作成してエクスポート、本コンポーネントの上段でインポートしたものです。クライアントからfirebaseに接続するための情報が入っております。2番目の引数はemail、3番目の引数はパスワードと決まっています。

その他本事例ではconst userCredentialに戻り値を格納するも使っておりません。サインアップするだけであれば戻り値を格納せずに、単にawait createUserWithEmailAndPasswordとすることも可能です。本事例のように戻り値を変数に格納するメリットとしては、.user属性により直ちに紐付く値(idなど)を保持するオブジェクトを取得できることです(下記ではuserに同オブジェクトを格納)。idが欲しい場合は、取得したオブジェクトに対してさらにuser.uidとします。

const user = userCredential.user;

updateProfile

本事例でユーザーは、メールとパスワードに加えてユーザー名もサインアップ時に一括して入力しましたが、厳密にはユーザー名はサインアップには使わないので、updateProfileから続く3行で、displayName属性に追加で保存します。updateProfile関数は、サインアップ済みのユーザーの属性(情報)を追加・更新する場合に使用し、引数は2つあります。1つ目の引数はログイン済みユーザーの認証情報(auth.currentUser)、2つ目の引数は更新する内容のオブジェクトです。また、ここではtryブロックの中に続く処理がないのでawait updateProfileとする必要はないですが、updateProfileの完了を前提とした別の処理が続く場合はawaitを付けます。

displayName以外にも、ユーザーのphotoUrl(アイコンのURL)をfirebaseのAuthenticationの機能として保存可能です。その他のユーザーに関連する情報を保存するためにはfirestoreを使う必要があります。本稿では詳細を書きませんが、firestoreにユーザーに関連する情報を保存するときは、Authenticationへの登録を済ませた後、firebaseから自動で生成されるidをfirestore内でユーザー情報を保存するためのドキュメントのidとして利用すること(RDBでいう外部キーの設定をすること)がポイントです。firestore一般については私のこちらの記事「React + Firestore【入門から実装まで】」を参考にしてください。また、firebase認証とfirestoreの連携については私のこちらの記事「Firebase認証とfirestoreの連携 – React」をご覧ください。

以上の処理が無事に完了した場合には、navigate(“/”);の部分で、ユーザーをルートディレクトリー(ホーム画面)にリダイレクトさせます。

catchブロックではエラーを処理します。本事例ではコンソル画面にエラーの内容を表示させていますが、実際にはユーザーに表示するなど他の処理を記述します。

const onSubmit = async (e) => {
e.preventDefault();
try {
const userCredential = await createUserWithEmailAndPassword(
auth,
email,
password
);
updateProfile(auth.currentUser, {
displayName: name,
});
navigate("/");
} catch (error) {
console.log(error);
}
};

return – jsx

最後はJSXの部分です。全体をdivタグで包み、内側にはformを置きます。先ほど説明したようにformにはonSubmit関数を呼び出すonSubmit属性を付けます。inputタグは同じ構造をしたものを、name、email、passwordに対応して3つ並べます。inputタグのそれぞれの属性の意味は、本稿をご覧になっている方には説明不要だ思いますので割愛します。onChange属性では、先ほど定義した同じ名前のonChange関数を呼び出します。formタグの最後にボタンを配置し、formに入力された情報をsubmitする(onSubmit属性をトリガーする)機能を持たせます。

return (
<div>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="Name"
id="name"
value={name}
required
onChange={onChange}
/>
<input
type="email"
placeholder="Email"
id="email"
value={email}
required
onChange={onChange}
/>
<input
type="password"
placeholder="Password"
id="password"
value={password}
required
onChange={onChange}
/>
<button type="submit">
Submit
</button>
</form>
</div>
);

ログイン済みユーザーの状態を読み込む方法

サインアップを完了すると、ログイン(サインイン)状態となります。下記は、先ほどご紹介したSignUpコンポーネントとは別のコンポーネントで、ログイン済みユーザーのDisplayNametとemailを取得・表示している例です。2行目で、コンフィギュレーションファイル(firebase.js)で設定したauthをインポートします。

関数の内側、1つのuseStateの中にオブジェクトとして複数の変数の状態を格納する使い方は、前述のSignUpコンポーネントと同じです。パスワードを画面上に表示することはあまりないので、この例ではdisplayNameとemailの2つを、ログイン済みユーザーの情報を保持するauth.currentUserから取り出し、それぞれをnameとemailに格納しています。

const { name, email } = formDataの部分では、オブジュトの分解(decompose)をしています。もちろん2つのuseState分けて記述しも問題なく、あるいはdecomposeをせずに使うことも可能です。最後のJSXの表示の部分では、ここでは簡易にpタグにそれぞれを表示しています。

Main.jsx

import { useState } from "react";
import { auth } from "../firebase.config";

function Main() {
const [formData, setFormData] = useState({
name: auth.currentUser.displayName,
email: auth.currentUser.email,
});
const { name, email } = formData;

return (
<div>
<p>{name}</p>
<p>{email}</p>
</div>
);
}

export default Main;

firebaseのauthenticationはデータベースではないので格納できるデータはこれまでご紹介したname, email, passwordに加えてユーザーに固有のidであるuid、ユーザーのアイコン画像のURLを格納するphothURL他一部に限られます。これ以上のデータを各ユーザーに紐づけて保存したい場合はfirestoreか、その他のDBが必要です。firestoreを使う場合は、auth.currentUser.uidをfirestoreのドキュメントのidとすれば、両者(authenticationとDB)のidが一致します。firestoreについては私のこちらの記事「React + Firestore【入門から実装まで】」で詳しく解説しています。

また、特定のページをログイン済みユーザーのみに表示させたいときは、protected (private) routeという構成を用います。Reactではreact-router-domライブラリーで実現できます。詳しくは私のこちらの記事「Reactで複数のページを作る方法 – Router v6.0準拠」のProtected (Private) Routeの章をご覧ください。

ログアウトの方法

下記は前の章で使用したMain.jsxにログアウトのボタンと機能を追加したものです。追加したコードは計4つのブロックです。以下、順に解説いたします。

Main.jsx

import { useState } from "react";
import { auth } from "../firebase.config";
import { useNavigate } from "react-router-dom"; // 追加1

function Main() {
const [formData, setFormData] = useState({
name: auth.currentUser.displayName,
email: auth.currentUser.email,
});
const { name, email } = formData;
const navigate = useNavigate(); // 追加2

// 追加3
const onLogout = () => {
auth.signOut();
navigate("/");
};

return (
<div>
<p>{name}</p>
<p>{email}</p>

// 追加4
<button type="button" onClick={onLogout}>
Logout
</
button>

</div>
);
}

export default Main;

useNavigate

useNavigateは、先ほどのSignUpコンポーネントでの使い方と同様にページの遷移に使います(ここではログアウト後の遷移)。

import { useNavigate } from "react-router-dom"; // 追加1

const navigate = useNavigate(); // 追加2

navigate("/"); // 追加3の一部

button

JSX内部でログアウトのためのボタンを追加します。ボタンがクリックされた時の処理をonClick属性に記述、ここではonLogoutという関数を呼び出しています。

// 追加4
<button type="button" onClick={onLogout}>
Logout
</button>

onLogout

onLogout関数(関数名は任意)で実際のログアウト処理を記述します。getAuth()メソッドで生成されたauthオブジェクトから、signOut()メソッドを呼び出すだけでログアウトできます。

// 追加3
const onLogout = () => {
auth.signOut();
navigate("/");
};

サインイン(ログイン)の実装方法

登録済み(=サインアップ済み)ユーザーのサインインは、サインアップとほとんど同じ構成で実装できます。違いは3点です。

1点目は、createUserWithEmailAndPasswordがsignInWithEmailAndPasswordになった点です。asyncのonSubmit関数の内側でawaitで使用する点、引数にauth、email、passwordを渡す点は同じです。

違いの2点目はホーム”/”への遷移にifの条件を付している点です。サインアップ時はそのまま”/”に遷移しましたが、サインインではサインインが確認できた場合に限り、すなわち、userCredential.userが存在する場合に限り、”/”に遷移させます。

3点目はフォームとsubmitする内容です。サインアップ時はnameの入力もしましたが、サインインではemailとパスワードでサインインするので、フォームへのnameの入力は不要です。

SignIn.jsx

import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { signInWithEmailAndPassword } from "firebase/auth"
import { auth } from "../firebase.config";

function SignIn() {
const [formData, setFormData] = useState({
email: "",
password: "",
});
const { email, password } = formData;
const navigate = useNavigate();

const onChange = (e) => {
setFormData({
...formData,
[e.target.id]: e.target.value,
});
};

const onSubmit = async (e) => {
e.preventDefault();
try {
const userCredential = await signInWithEmailAndPassword(
auth,
email,
password
);
if (userCredential.user) {
navigate("/");
}
} catch (error) {
console.log(error);
}
};

return (
<div>
<form onSubmit={onSubmit}>
/>
<input
type="email"
placeholder="Email"
id="email"
value={email}
required
onChange={onChange}
/>
<input
type="password"
placeholder="Password"
id="password"
value={password}
required
onChange={onChange}
/>
<button type="submit">
Submit
</button>
</form>
</div>
);
}

export default SignIn;

Firebaseがユーザーの状態を保存するブラウザ内の場所 – IndexedDB

サインアップとログインを機能させるには、ユーザーの状態、すなわちユーザーがログインしているか否かをユーザー(クライアント)のブラウザ上に保存する必要があります。FirebaseではIndexed DBに保存しています。実際に保存されている内容を確認するには、Chromeのコンソル画面を開き、”>>”のマークをクリック、続いてApplicationをクリックします。Storege内にあるIndexedDB – firebaseLocalStorageにユーザーの状態が格納されています。下記画面では隠してありますが、右側でオブジュトの内容を確認できます。


Firebase chrome Indexed DB

保存されているデータを消去する方法

firebaseLocalStorageと書かれた部分を右クリックして”clear”をクリックすると保存されているユーザーのデータが消えます(ログアウトされます)。


Firebase chrome Indexed DB delete the information stored / 情報を消去する方法

今日も最後まで読んで頂きありがとうございました。私のこちらの記事「React + Firebaseを使ったユーザー認証の実装方法」ではfirebaseの各Authenticationに共通の設定に加えて、gmailによるサインアップを、こちらの記事「Facebook認証の実装方法 – Firebase + React」ではfacebookによるサインアップを解説していますので、宜しければ併せてご覧になってみてください。