📑

Firebase SDK v9、RecoilでNext.jsアプリのGoogleログインを実装する

2021/09/22に公開
1

はじめに

この記事ではTypeScriptで書かれたNextアプリにFirebase JavaScript SDK v9を利用してGoogleログインを実装する方法について書きます。
認証するユーザはRecoilで管理します。
Context APIで管理する方法はこちらの記事で書いています。
また、前述した記事に載っていますので、この記事ではFirebaseプロジェクトの作成、Googleプロバイダの有効化などの詳しい手順は省略させて頂きます。

この記事が他の人の参考になれば幸いです。
また、この記事の内容に間違った記載がありましたら、指摘してもらえるとありがたいです。

環境

名前 バージョン
macOS Big Sur 11.6
Node.js 16.9.0
TypeScript 4.4.3
React 17.0.2
Next.js 11.1.2
Recoil 0.4.1
Firebase JavaScript SDK 9.0.2

事前準備

NextアプリにGoogleログインを実装するコードを書いていく前にFirebaseプロジェクト等を作る必要があります。
主に以下のことを事前に行う必要があります。

  • Firebaseプロジェクトの作成
  • Authentication、Googleプロバイダの有効化
  • Webアプリの追加とその設定情報のメモ

詳しくはこちらの記事で行なっていますので、必要に応じて参照してください。

環境変数ファイルを作成する

Firebaseの設定情報はコード上に記述し、バージョン管理したくないので環境変数ファイル.env.localを作成し、事前準備で得た設定情報を記述しておきます。

.env.local
NEXT_PUBLIC_FIREBASE_API_KEY=<apiKey>
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=<authDomain>
NEXT_PUBLIC_FIREBASE_PROJECT_ID=<projectId>
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=<storageBucket>
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=<messagingSenderId>
NEXT_PUBLIC_FIREBASE_APP_ID=<appId>

firebaseライブラリのインストールとセットアップ

firebaseライブラリをインストールし、そのセットアップを行います。

terminal
npm i firebase

インストール後、任意のファイル(この記事ではlib/firebase.ts)でfirebaseライブラリのセットアップを行います。
環境変数ファイル.env.localからFirebaseの設定情報を読み込み、firebaseライブラリの初期化をしています。
また、appは他のファイルで使用するのでエクスポートしています。

lib/firebase.ts
import { initializeApp } from "firebase/app";

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

export const app = initializeApp(firebaseConfig);

参考:Add Firebase to your JavaScript project

ログイン、ログアウトを行う関数を定義する

任意のファイル(この記事ではlib/auth.ts)にログイン、ログアウトを行う関数を定義します。lib/auth.tsに以下のように記述しました。

lib/auth.ts
import { getAuth, signInWithRedirect, signOut, GoogleAuthProvider } from "firebase/auth";

import { app } from "./firebase";

export const login = (): Promise<void> => {
  const provider = new GoogleAuthProvider();
  const auth = getAuth(app);
  return signInWithRedirect(auth, provider);
};

export const logout = (): Promise<void> => {
  const auth = getAuth(app);
  return signOut(auth);
};

signInWithRedirect()でGoogleログイン用のページにリダイレクトし、Googleログインを行います。
また、provider、authのプロパティを変更することで言語などを変更、設定できます。
詳しくはAuthenticate Using Google Sign-In with JavaScriptなど参照してください。
signOut()でログアウトします。

ユーザ認証を管理する関数を定義する

Recoilでユーザ認証を管理します。lib/auth.tsに以下のように追記しました。

lib/auth.ts
import { useEffect, useState } from "react";
import { atom, useRecoilValue, useSetRecoilState } from "recoil";
import {
  User,
  getAuth,
  signInWithRedirect,
  signOut,
  onAuthStateChanged,
  GoogleAuthProvider,
} from "firebase/auth";

import { app } from "./firebase";

type UserState = User | null;

const userState = atom<UserState>({
  key: "userState",
  default: null,
  dangerouslyAllowMutability: true,
});

export const login = (): Promise<void> => {
  const provider = new GoogleAuthProvider();
  const auth = getAuth(app);
  return signInWithRedirect(auth, provider);
};

export const logout = (): Promise<void> => {
  const auth = getAuth(app);
  return signOut(auth);
};

export const useAuth = (): boolean => {
  const [isLoading, setIsLoading] = useState(true);
  const setUser = useSetRecoilState(userState);

  useEffect(() => {
    const auth = getAuth(app);
    return onAuthStateChanged(auth, (user) => {
      setUser(user);
      setIsLoading(false);
    });
  }, [setUser]);

  return isLoading;
};

export const useUser = (): UserState => {
  return useRecoilValue(userState);
};

userStateは認証するユーザを保持するRecoilの状態です。
ユーザがログインしている場合はUser、ログインしていない場合はnullになります。useUser()はそのuserStateを他のコンポーネントで呼び出すための関数です。
useAuth()がユーザ認証を監視するための関数です。
onAuthStateChanged()メソッドはユーザ認証を監視し、変更があったときに引数のコールバック関数を実行します。
その関数内でユーザの状態を更新しています。
isLoadingonAuthStateChanged()を実行中か確認するための状態です。この状態の値がtrueの時はonAuthStateChanged()でユーザを認証中です。

ログイン、ログアウト、ユーザ認証の管理を実装する

上記で定義した関数を使用し、実際にユーザ認証を実装します。
まず、pages/_app.tsxに以下のように追記しました。

pages/_app.tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { RecoilRoot } from "recoil";

import { useAuth } from "../lib/auth";

type Props = {
  children: JSX.Element;
};

const Auth = ({ children }: Props): JSX.Element => {
  const isLoading = useAuth();

  return isLoading ? <p>Loading...</p> : children;
};

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <RecoilRoot>
      <Auth>
        <Component {...pageProps} />
      </Auth>
    </RecoilRoot>
  );
}

export default MyApp;

useAuth()<RecoilRoot>の子孫コンポーネントでしか使用できないので<Auth>コンポーネントを作成し、その中で使用しています。
ユーザ認証中は<p>Loading...</p>のみが表示されます。

ログイン、ログアウトは以下のように任意の場所で関数を呼び出し、行ないます。
また、useUser()も以下のように使用し、ユーザを取得できます。

pages/index.tsx
...
import { useUser, login, logout } from "../lib/auth";

const Home: NextPage = () => {
  const user = useUser();

  const handleLogin = (): void => {
    login().catch((error) => console.error(error));
  };

  const handleLogout = (): void => {
    logout().catch((error) => console.error(error));
  };

  return (
    <div className={styles.container}>
      <Head>
        <title>Auth Example</title>
      </Head>

      <div>
        <h1>Auth Example</h1>
        {user !== null ? (
          <h2>ログインしている</h2>
        ) : (
          <h2>ログインしていない</h2>
        )}
        <button onClick={handleLogin}>ログイン</button>
        <button onClick={handleLogout}>ログアウト</button>
      </div>
    </div>
  );
};
...

参考

GitHubで編集を提案

Discussion

ニツオニツオ

どんぴしゃな記事で助かります!意外とfirebase v9の記事は少なく。
・・・しかし、Recoil新しすぎて、手出しにくいです~、、
出来れば、Reduxか、React Hooksを使ったVersionも別記事か本記事の更新版なのか、公開してほしいです!