Firebaseの勉強のため、Firebase Authenticationで認証機能を実装します。
メールアドレスとパスワードで認証します。
使用技術
- Firebase Authentication
- React.js
- TypeScript
1. Reactプロジェクトを作成する
私は自分用のReactテンプレートがあるので、それを使います。
しかしcreate-react-app
でも問題なく手順を実行できると思います。
npx create-react-app firebase-auth-practice --template typescript
プロジェクトルートで以下を実行してFirebaseをインストールします。
npm i firebase
2. Firebaseプロジェクトを作成する
Firebaseのコンソールにアクセスします。
https://console.firebase.google.com/
「プロジェクトを追加」をクリックします。
プロジェクト名を入力して「続行」を押下します。
「続行」を押下します。
Google アナリティクスのアカウントを選択してから「プロジェクトを作成」を押下します。
※初回はアカウントがないので、作成します
待ちます。
完了したら「続行」を押下します。
「ウェブ」を押下します。
アプリのニックネームを適当に入力してから「アプリを登録」を押下します。
「コンソールに進む」を押下します。
コンソールに戻るので、左のドロワーの「Authentication」を選択します。
「始める」を押下します。
「メール/パスワード」を選択します。
「メール/パスワード」を有効にします。
「メールリンク(パスワードなしでログイン)」は使いたい場合は有効にします。(後から設定可能)
「保存」を押下します。
左のドロワーの「プロジェクトの設定」を選択します。
下にスクロールして、JavaScriptのコードをコピーします。
3. Reactプロジェクトに認証機能を追加
src/index.tsx
に先程コピーしたコードを貼り付けます。
// src/index.tsx
import { getAnalytics } from "firebase/analytics";
import { initializeApp } from "firebase/app";
import React from "react";
import ReactDOM from "react-dom";
const firebaseConfig = {
apiKey: "AIzaSyATzDcdnsLQXWLEIittbRgtu_sRUfEZXgQ",
authDomain: "auth-practice-5fbad.firebaseapp.com",
projectId: "auth-practice-5fbad",
storageBucket: "auth-practice-5fbad.appspot.com",
messagingSenderId: "762238832983",
appId: "1:762238832983:web:b36f3fa07673ba6f896e12",
measurementId: "G-SJSN6K80KR",
};
const app = initializeApp(firebaseConfig);
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
const analytics = getAnalytics(app);
ReactDOM.render(<div></div>, document.getElementById("root"));
カスタムフックで認証ロジックを実装します。
src/hooks
ディレクトリを作成し、そこにauth.tsx
を以下のように実装します。
// src/hooks/auth.tsx
import React, { useEffect, useState, useContext, createContext } from "react";
import {
getAuth,
createUserWithEmailAndPassword,
onAuthStateChanged,
} from "firebase/auth";
import type { User } from "firebase/auth";
interface UseProvideAuth {
isFirstLoading: boolean;
isAuthenticated: boolean;
user: User | null;
signUp: (email: string, password: string) => Promise<boolean>;
}
const authContext = createContext({} as UseProvideAuth);
export const ProvideAuth: React.FC = ({ children }) => {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
};
export const useAuth = () => {
return useContext(authContext);
};
const useProvideAuth = (): UseProvideAuth => {
const [isFirstLoading, setIsFirstLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState<User | null>(null);
// アプリケーションの最初のレンダリング直後に認証を確認するロジック
useEffect(() => {
const auth = getAuth();
onAuthStateChanged(auth, (user) => {
if (user) {
setUser(user);
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
}
setIsFirstLoading(false);
});
}, []);
const signUp = async (email: string, password: string) => {
const auth = getAuth();
// サインアップ処理
return createUserWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
setUser(userCredential.user);
setIsAuthenticated(true);
return true;
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
console.log(errorCode);
console.log(errorMessage);
setIsAuthenticated(false);
return false;
});
};
return {
isFirstLoading,
isAuthenticated,
user,
signUp,
};
};
src/App.tsx
から先程のhookを実行して登録フォームを実装します。
// src/App.tsx
import React, { useState } from "react";
import { useAuth } from "./hooks/auth";
export const App = () => {
const auth = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
if (auth.isFirstLoading) {
return <div>check authentication...</div>;
}
const signUp = () => {
auth.signUp(email, password)
.then(() => {
console.log("登録に成功しました");
})
.catch(() => {
console.log("登録に失敗しました...");
})
.finally(() => {
setEmail("");
setPassword("");
});
};
return (
<div>
<p>
{auth.isAuthenticated
? "ログイン中です"
: "ログインしていません"}
</p>
<p>
mail
<input
type="email"
name="email"
id="email"
value={email}
onChange={(e) => {
setEmail(e.target.value);
}}
/>
</p>
<p>
password
<input
type="password"
name="password"
id="password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
</p>
<input type="button" value="会員登録" onClick={signUp} />
</div>
);
};
src/index.tsx
を以下のように修正して、hookをアプリケーション全体に反映します。
import { getAnalytics } from "firebase/analytics";
import { initializeApp } from "firebase/app";
import React from "react";
import ReactDOM from "react-dom";
// 追加
import { App } from "./App";
import { ProvideAuth } from "./hooks/auth";
const firebaseConfig = {
apiKey: "AIzaSyATzDcdnsLQXWLEIittbRgtu_sRUfEZXgQ",
authDomain: "auth-practice-5fbad.firebaseapp.com",
projectId: "auth-practice-5fbad",
storageBucket: "auth-practice-5fbad.appspot.com",
messagingSenderId: "762238832983",
appId: "1:762238832983:web:b36f3fa07673ba6f896e12",
measurementId: "G-SJSN6K80KR",
};
const app = initializeApp(firebaseConfig);
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
const analytics = getAnalytics(app);
ReactDOM.render(
// 修正
<ProvideAuth>
<App />
</ProvideAuth>,
document.getElementById("root")
);
これで実装は完了です。
以下で動作確認します。
npm run dev
登録前
登録後
FirebaseコンソールのAuthenticationのUsersタブを見ると、ユーザーが登録されていることが確認できます。
【疑問】firebaseConfigは公開しても良いのか?
AWS Amplifyでは、このようなメタ情報はgitignoreにして公開しなかったので、Firebaseでは公開して大丈夫なのか気になりました。
調べてみると、どうやら基本的に公開して問題ないようです。
https://firebase.google.com/docs/projects/api-keys#api-keys-for-firebase-are-different
上記リンクからの引用
FirebaseサービスのAPIキーをシークレットとして扱う必要はありませんが、APIキーの誤用からプロジェクトを保護するために、追加の対策を講じる必要がある特定のケースがいくつかあります。
stackoverflowにもこれについての質問がありました。
https://stackoverflow.com/questions/37482366/is-it-safe-to-expose-firebase-apikey-to-the-public
まとめ
とりあえず登録機能だけ実装しましたが、ログインやログアウトなども同じようにsrc/hooks/auth.tsx
に実装すれば良いです。
Firebaseは初めて使いましたが、短時間で認証機能を導入でき、コンソールのUIも見やすくて良いサービスだと思いました。
今後はソーシャルアカウント認証やゲスト認証なども実装するつもりなので、この記事に追記するかもしれません。
最後までお読みいただきありがとうございました!
参考リンク
Firebaseドキュメント
FirebaseをJavaScriptプロジェクトに追加する
ウェブサイトでFirebase Authenticationを使ってみる