🌟

Next.js(TypeScript)でFirebaseを利用し、Googleログインを実装する

2021/07/18に公開
1

はじめに

この記事では、Firebase Authenticationを使ってTypeScriptを使用したNextアプリにGoogleログインを実装する方法を記述します。
JavaScriptを使用したNextアプリにGoogleログインを実装する方法はこちらに記述しています。

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

環境

名前 バージョン
macOS Big Sur 11.4
Node.js 16.4.1
TypeScript 4.3.5
React 17.0.2
Next.js 11.0.1

適当なNextアプリの作成

以下のコマンドでauth-exampleというNextアプリを作成しました。
このアプリにGoogleログインを実装していきます。

terminal
npx create-next-app --ts --use-npm auth-example

Firebaseの設定

以下の流れでFirebaseの設定を行っていきます。

  1. Firebaseプロジェクトの作成
  2. Authenticationの有効化
  3. Googleプロバイダの有効化
  4. (任意)承認済みドメインの設定
  5. ウェブアプリの追加

Firebase consoleにアクセスし、Firebaseプロジェクトを作成します。
適当なプロジェクト名を入力し、プロジェクトを作成します。
Googleアナリティクスの設定は任意です。

プロジェクトの作成後、左側のナビゲーションからAuthenticationのページに飛び、[始める]ボタンでAuthenticationを有効にします。

その後、AuthenticationのSign-in methodタブでGoogleプロバイダを有効にします。
プロジェクトの公開名プロジェクトのサポートメールは適当なものを設定し、保存します。

また、Sign-in methodタブのページ下部で承認済みドメインの設定ができます。
今回は変更しません。

左側のナビゲーションからプロジェクトの概要に飛び、</>のボタンでウェブアプリを追加します。
適当なアプリのニックネームを入力し、アプリを登録してください。
その後、Firebase SDKの追加で表示されるソースコードは、Nextアプリで使用するので、コピーして保存しておきます。

NextアプリでのGoogleログインの実装

以下の流れでNextアプリにGoogleログインを実装します。

  1. 環境変数ファイル.env.localの作成
  2. Firebaseライブラリのセットアップ
  3. ログイン処理の実装

環境変数ファイル.env.localの作成

プロジェクトディレクトリに.env.localファイルを作成し、環境変数を設定します。
先ほどコピーしたソースコードのfirebaseConfig変数のプロパティ値を環境変数として読み込むために以下のように記述します。
NEXT_PUBLIC_のプリフィックスはクライアントで動作するNextアプリが環境変数を読み込むために必要です。
また、Googleアナティクスの有効無効によって、設定する環境変数の数が以下の場合と異なる可能性があります。

.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ライブラリのセットアップを行います。

lib/firebase.tsは以下のように記述しました。
環境変数からFirebaseの設定情報を読み込み、firebaseライブラリを初期化しています。
また、再レンダリングなどの際に、初期化が複数回行われないようにif文で条件分岐させています。

lib/firebase.ts
import firebase from "firebase/app";
import "firebase/auth";

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,
};

if (firebase.apps.length === 0) {
    firebase.initializeApp(firebaseConfig);
}

export const auth = firebase.auth();
export default firebase;

ログイン処理の実装

実際のログイン処理を実装します。

この記事では、ユーザの認証関連の情報をuseContextを使用し、グローバルで扱います。
そのため、lib/AuthContextに認証関連の情報を扱うコンテキストを、pages/_app.jsにコンテキストを読み込む処理を、pages/index.jsにログイン、ログアウト関数の呼び出しを記述します。

以下の流れで実装していきます。

  1. lib/AuthContext.tsxAuthContextの作成
  2. pages/_app.tsxAuthContextの読み込み
  3. pages/index.tsxAuthContextの使用

lib/AuthContext.tsxAuthContextの作成

lib/AuthContext.tsxを作成し、以下のように記述しました。

createContextでユーザ、ログインやログアウトを扱う関数を保持するAuthContextを作成しています。
AuthContextはカスタムHookuseAuthを通して使用できます。
login関数のauth.signInWithRedirect関数でリダイレクトでログインする方法を使用していますが、ポップアップを表示し、ログインする方法も存在します。

lib/AuthContext.tsx
import { createContext, useState, useEffect, useContext } from "react";
import { User } from "@firebase/auth-types";

import firebase, { auth } from "../lib/firebase";

type AuthContextType = {
    currentUser: User | null;
    login?: () => Promise<void>;
    logout?: () => Promise<void>;
};

const AuthContext = createContext<AuthContextType>({ currentUser: null });

export const useAuth = () => {
    return useContext(AuthContext);
};

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

const AuthProvider = ({ children }: Props): JSX.Element => {
    const [currentUser, setCurrentUser] = useState<User | null>(null);
    const [isLoading, setIsLoading] = useState(true);

    const login = () => {
        const provider = new firebase.auth.GoogleAuthProvider();
        return auth.signInWithRedirect(provider);
    };

    const logout = () => {
        return auth.signOut();
    };

    useEffect(() => {
        return auth.onAuthStateChanged((user: User | null) => {
            setCurrentUser(user);
            setIsLoading(false);
        });
    }, []);

    const value: AuthContextType = {
        currentUser,
        login,
        logout,
    };

    return (
        <AuthContext.Provider value={value}>
            {isLoading ? <p>Loading...</p> : children}
        </AuthContext.Provider>
    );
};

export default AuthProvider;

pages/_app.tsxAuthContextの読み込み

pages/_app.tsxを以下のように変更しました。

<AuthProvider><Component>の親コンポーネントにすることで全てのコンポーネントからAuthContextの情報にアクセスできるようにしています。

pages/_app.tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import AuthProvider from "../lib/AuthContext";

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

export default MyApp

pages/index.tsxAuthContextの使用

pages/index.tsxを以下のように変更しました。

これはuseAuthを使ってAuthContextを利用する一つの例です。
currentUserの値でユーザがログインしているかどうかの判別ができます。

pages/index.tsx
import styles from '../styles/Home.module.css'
import {useAuth} from "../lib/AuthContext";

export default function Home() {
const {currentUser, login, logout} = useAuth()

  return (
    <div className={styles.container}>
      <main className={styles.main}>
          {!currentUser && <button onClick={login}>ログイン</button>}
          {currentUser &&
          <div>
              <p>{currentUser.email} でログイン中</p>
              <button onClick={logout}>ログアウト</button>
          </div>}
      </main>
    </div>
  )
}
GitHubで編集を提案

Discussion

yudai_sunyudai_sun

firebaseがv9だとimportの記述の仕方が違うのでerrorが出てくると思います。

あと、自分のローカルで参考にコードを書いたところログインボタンからログインを行ってもずっとログインボタンのままなのですが何か原因にお心当たりがございますでしょうか?