Firebase Authentication(+ Next.js + TypeScript) 素振り
はじめに
こんばんは、かべです。
ちゃんとした Web アプリを作るにはやっぱりユーザー認証機能が欲しい!ということで新規ユーザー作成、ログイン機能を実装する練習をしました。先日 Firebase の Realtime Database を使うことがあったので、流れで FIrebase Authentication を使うことにしました。
いろいろやり方を探していると良さげな Zennの記事 を見つけたため、これをなぞりながら自分でも少し機能を足していこうと思います。今回の目標は次の通りです。
- 新規ユーザー登録がメールアドレスとパスワードで出来る
- 登録したユーザーのメールアドレスとパスワードでログインできる
- ログインするとログイン後のページに飛び、「ようこそ、〇〇さん!」のように表示される
- ログインしていない時にログイン後のページを見ようとしても戻される
構成としては、Next.js + TypeScript で行います。
環境
今回の開発は以下の環境で行いました。
- Ubuntu 20.04.1
- yarn 1.22.5
- firebase 8.3.2
- Next 10.1.3
- React 17.0.2
- TypeScript 4.0
Firebase の設定
- Firebase のコンソールから「プロジェクトを追加」
- 適当にプロジェクト名等入力
- 左サイドバーの「Authentication」をクリック
- 「Sign-in method」から「メール/パスワード」を有効にする
- 左サイドバーの「プロジェクトを設定」から Web アプリを追加し、Firebase SDK を追加する
という手順で簡単に設定できます。最後の手順で出てくる var firebaseConfig
は保存しておいてください。接続の際に必要です。
Next.js の初期設定と Firebase への接続
いつもの create-next-app
で作ります。今回は TypeScript を使いたいので npx create-next-app --example with-typescript [好きなプロジェクト名]
で行きます。完成したらいらないファイルを消して、そこにいろいろ書いていきましょう。
まず、Firebase SDK にある Firebase への接続キーを Next.js 上で読めるようにします。Next.js では .env
ファイルに書くだけでは環境変数をよしなに読み取ってくれないようで、next.config.js
ファイルにも書く必要があります。生成されたコードのルートディレクトリに次のコードを追加しましょう。
.env.local
FIREBASE_API_KEY='hoge' FIREBASE_AUTH_DOMAIN='hoge' FIREBASE_PROJECT_ID='hoge' FIREBASE_STORAGE_BUCKET='hoge' FIREBASE_MESSAGING_SENDER_ID='hoge' FIREBASE_APP_ID='hoge'
hoge
には適宜得られた値を代入します。
next.config.js
module.exports = { env: { FIREBASE_API_KEY: process.env.FIREBASE_API_KEY, FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN, FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID, FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET, FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID, FIREBASE_APP_ID: process.env.FIREBASE_APP_ID, }, };
環境変数が設定出来たら、次は Next.js 内で firebase のあれこれを使えるように firebase 利用の大元となるファイルを作ります。
/utils/firebase.ts
import "firebase/auth"; import "firebase/firestore"; import firebase from "firebase/app"; const firebaseConfig = { apiKey: process.env.FIREBASE_API_KEY, authDomain: process.env.FIREBASE_AUTH_DOMAIN, projectId: process.env.FIREBASE_PROJECT_ID, storageBucket: process.env.FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID, appId: process.env.FIREBASE_APP_ID, }; // 複数回呼ばれるとエラー if (!firebase.apps.length) { firebase.initializeApp(firebaseConfig); } export const auth = firebase.auth();
ユーザー登録
今回は localhost:3000
の/
をログイン用のページ、/signup
をユーザー登録用のページ、/contents
をログイン成功後のページとして作成していきます。
まずはユーザー情報を Context として持つための Provider などを /context/Auth.tsx
に作ります。
/context/Auth.tsx
import firebase from "firebase/app"; import { FC, createContext, useEffect, useState } from "react"; import { auth } from "../utils/firebase"; // 現在のユーザーを持つ Context の型 type AuthContextProps = { currentUser: firebase.User | null | undefined; }; const AuthContext = createContext<AuthContextProps>({ currentUser: undefined }); // Context を全体で使えるようにする Provider const AuthProvider: FC = ({ children }) => { const [currentUser, setCurrentUser] = useState< firebase.User | null | undefined >(undefined); // ログイン状態が変わった時に発火 useEffect(() => { auth.onAuthStateChanged((user) => { setCurrentUser(user); }); }, []); return ( <AuthContext.Provider value={{ currentUser }}> {children} </AuthContext.Provider> ); }; export { AuthContext, AuthProvider };
/pages/_app.tsx
で全体に当てるのも忘れずに。
/pages/_app.tsx
import { AppProps } from "next/app"; import { AuthProvider } from "../context/Auth"; const MyApp = ({ Component, pageProps }: AppProps) => { return ( <AuthProvider> <Component {...pageProps} /> </AuthProvider> ); }; export default MyApp;
ここまで下準備が出来たら、登録用のフォームを作ります。これは firebase.auth()
が持つ createUserWithEmailAndPassword
を使って簡単に実装出来ます。
/pages/signup.tsx
import { NextPage } from "next"; import { useRouter } from "next/router"; import Link from "next/link"; import { FormEvent, useEffect, useState } from "react"; import { auth } from "../utils/firebase"; const SignUp: NextPage = () => { const router = useRouter(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); // 既にサインインしている時はサインアップの必要が無いので // ログイン後のページに飛ばす useEffect(() => { auth.onAuthStateChanged((user) => { user && router.push("/contents"); }); }, []); // 新規ユーザー登録 const createUser = async (e: FormEvent) => { // form の動作を止める e.preventDefault(); try { // firebase のユーザー登録用メソッド await auth.createUserWithEmailAndPassword(email, password); router.push("/"); } catch (error) { alert(error.message); } }; return ( <> <form onSubmit={createUser}> <div> <label htmlFor="email">メールアドレス: </label> <input id="email" type="email" onChange={(e) => setEmail(e.target.value)} /> </div> <div> <label htmlFor="password">パスワード: </label> <input id="password" type="password" onChange={(e) => setPassword(e.target.value)} /> </div> <button type="submit">登録</button> </form> <Link href="/">ログイン</Link> </> ); }; export default SignUp;
ログイン
次にログイン機能を実装していきましょう。これも firebase.auth()
が持つ signInWithEmailAndPassword
で簡単に実装出来ます。
/pages/index.tsx
import { NextPage } from "next"; import Link from "next/link"; import { useRouter } from "next/router"; import { FormEvent, useEffect, useState } from "react"; import { auth } from "../utils/firebase"; const IndexPage: NextPage = () => { const router = useRouter(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); // 既にサインインしている時はサインアップの必要が無いので // ログイン後のページに飛ばす useEffect(() => { auth.onAuthStateChanged((user) => { user && router.push("/contents"); }); }, []); // ログイン処理 const logIn = async (e: FormEvent) => { // form の動作を止める e.preventDefault(); try { await auth.signInWithEmailAndPassword(email, password); router.push("/contents"); } catch (err) { alert(err.message); } }; return ( <> <form onSubmit={logIn}> <div> <label htmlFor="email">メールアドレス: </label> <input id="email" type="email" onChange={(e) => setEmail(e.target.value)} /> </div> <div> <label htmlFor="password">パスワード: </label> <input id="password" type="password" onChange={(e) => setPassword(e.target.value)} /> </div> <button type="submit">ログイン</button> </form> <Link href="/signup">新規登録</Link> </> ); }; export default IndexPage;
ログインが出来たら /contents
に飛ばしてあげます。/contents
ではログインしたユーザーのメールアドレスを表示してみましょう。ここでは context を使って実装しています。
/pages/contents.tsx
import { NextPage } from "next"; import { useRouter } from "next/router"; import { useContext, useEffect } from "react"; import { auth } from "../utils/firebase"; import { AuthContext } from "../context/Auth"; const Contents: NextPage = () => { const router = useRouter(); // Context を使用 const currentUser = useContext(AuthContext); useEffect(() => { // ログインしていない状態で見ようとした時にログイン画面へ戻す auth.onAuthStateChanged((user) => { user || router.push("/"); }); }, []); const logOut = async () => { try { await auth.signOut(); router.push("/"); } catch (error) { alert(error.message); } }; // ログインしていない時に見ようとしても見られないようにする return ( <> {currentUser.currentUser ? ( <> <div>ようこそ、{currentUser.currentUser?.email}さん</div> <button onClick={logOut}>ログアウト</button> </> ) : ( <></> )} </> ); }; export default Contents;
JSX 部分で3項演算子での分岐をしているのは、ログインしていない状態で見ようとした時に何もレンダリングされないようにするためです。この部分を消すと localhost:3000/contents
を直接叩いたときに「ようこそ、さん」と一瞬表示されてしまいます。
完成
これで完成です!ユーザー登録、ログインなどが出来るか確認してみましょう。
終わりに
Firebase Authentication でログイン機能を簡単に実装することが出来ました。次は NextAuth,js でも同じようなことをして比べてみたいですね。
今回のコードはこちらにアップロードされています。