🌵

Next.jsでFirebase Authenticationを使う(with Context API)

2021/04/25に公開

はじめに

自分が作ろうとしているサービスにログイン機能が必要である。
ログイン機能は Firebase Authentication を使って認証したい。
Next.js で Firebase Authentication を使う際に、Context API を使うと初見ではややこしかったのでまとめていく。

環境

  • Next.js v10.1.3
  • Chakra UI
  • TypeScript v4.2.3
  • Firebase v9.1.0

ステート管理は Context API と Hooks を使う

プロジェクト作成

Firebase の設定は一からしたいので、TypeScript・Chakra UI の公式サンプルを使用する。

$ npx create-next-app firebase-sample --example with-chakra-ui-typescript

Firebase の設定

Firebase で新規プロジェクトを作成

以下コンソールより新規プロジェクトを作成する。
https://firebase.google.com/

プロジェクト全般 -> 全般より API キーなどの情報を確認し、.env.localに環境変数を設定する。

Firebase のインストール

$ yarn add firebase

Firebase の初期化

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

const config = {
  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_MESSEGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

const firebaseApp = initializeApp(config);
export default firebaseApp;

ユーザー情報の抽象化

firebase の情報を UI 層へ露出しないように一旦ユーザー情報を抽象化する。

/src/framework/common/User.ts
export type User = {
  displayName: string | null | undefined;
  email: string | null | undefined;
};

このようにしておくことで認証基盤を変更するときに UI 層を変更せずに済むので便利。

ログイン等の機能を実装

ログイン、ログアウトなど必要な機能を実装していく。

/src/framework/firebase/auth.ts
import firebaseApp from "./firebase";
import {
  getAuth,
  onAuthStateChanged as onFirebaseAuthStateChanged,
  signInWithRedirect,
  GoogleAuthProvider,
  signOut,
} from "firebase/auth";
import { User } from "framework/common";

const provider = new GoogleAuthProvider();

export function login(): void {
  const auth = getAuth(firebaseApp);
  signInWithRedirect(auth, provider);
}

export function logout(): Promise<void> {
  return new Promise((resolve, reject) => {
    const auth = getAuth(firebaseApp);
    signOut(auth)
      .then(() => resolve())
      .catch((error) => reject(error));
  });
}

export const onAuthStateChanged = (callback: (user: User | null) => void) => {
  const auth = getAuth(firebaseApp);

  onFirebaseAuthStateChanged(auth, (user) => {
    const userInfo: User | null = user
      ? {
          displayName: user?.displayName,
          email: user?.email,
        }
      : null;
    callback(userInfo);
  });
};

Context API を用いて AuthProvider を作る

Context API について

コンテクストは、ある React コンポーネントのツリーに対して「グローバル」とみなすことができる、現在の認証済みユーザ・テーマ・優先言語といったデータを共有するために設計されています。

https://ja.reactjs.org/docs/context.html

使い方はざっくりと以下の感じ

  1. React.createContext(defaultValue)で Context を作成する
  2. <Context.Provider value={data}></Context.Provider>で値をセットする。
  3. 使う側はuseContext(1で作ったContext)で 2 で設定したデータを取得する

AuthContext と Provider を作成する

/src/framework/context/AuthContext.tsx
import { createContext, useEffect, useState, useContext } from "react";
import { User } from "framework";
import { onAuthStateChanged } from "@auth";

type AuthContextProps = {
  currentUser: User | null | undefined;
};

const AuthContext = createContext<AuthContextProps>({ currentUser: undefined });

export const AuthProvider: React.FC = ({ children }) => {
  const [currentUser, setCurrentUser] = useState<User | null | undefined>(
    undefined
  );

  useEffect(() => {
    onAuthStateChanged((user) => {
      // ログイン状態が変化すると呼ばれる
      setCurrentUser(user);
    });
  }, []);
  return (
    <AuthContext.Provider value={{ currentUser: currentUser }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuthContext = () => useContext(AuthContext);

/src/pages/_app.tsx
import { ChakraProvider } from '@chakra-ui/react'

import theme from '../theme'
import { AppProps } from 'next/app'
import { AuthProvider } from 'framework';

function MyApp({ Component, pageProps }: AppProps) {
  return (
+   <AuthProvider>
      <ChakraProvider resetCSS theme={theme}>
        <Component {...pageProps} />
      </ChakraProvider>
+   </AuthProvider>
  )
}

export default MyApp

動かしてみる

ログイン・ログアウトボタンを作る

/src/components/Button.tsx
import { Button } from "@chakra-ui/react";
import { login, logout } from "@auth";

type BaseButtonProps = {
  text: string;
  onclick(): void;
};
const BaseButton: React.FC<BaseButtonProps> = (props) => (
  <Button
    backgroundColor="#48BB78"
    color="#fff"
    width="100px"
    height={{ base: "35px", sm: "35px", md: "35px", lg: "35px" }}
    marginRight={2}
    onClick={props.onclick}
  >
    {props.text}
  </Button>
);

const LoginButton = () => <BaseButton onclick={() => login()} text="LOGIN" />;

const LogoutButton = () => (
  <BaseButton onclick={() => logout()} text="LOGOUT" />
);

export { BaseButton, LoginButton, LogoutButton };

/src/pages/_app.tsx
import { Center } from "@chakra-ui/react";
import { Hero } from "../components/Hero";
import { Container } from "../components/Container";
import { Main } from "../components/Main";
import { DarkModeSwitch } from "../components/DarkModeSwitch";
import { LoginButton, LogoutButton } from "../components/Button";
import { useAuthContext } from "framework";

const Index = () => {
  const { currentUser } = useAuthContext();

  return (
    <Container height="100vh">
      <Hero />
      <Main>
        <Center>{currentUser ? <LogoutButton /> : <LoginButton />}</Center>
      </Main>
      <DarkModeSwitch />
    </Container>
  );
};

export default Index;

未ログイン時は LOGIN ボタンが表示される。

ログイン時は LOGOUT ボタンが表示される。

まとめ

Context API はややこしいがざっくりこんな感じで使う

  1. React.createContext(defaultValue)で Context を作成する
  2. <Context.Provider value={data}></Context.Provider>で値をセットする。
  3. 使う側はuseContext(1で作ったContext)で 2 で設定したデータを取得する
    Context にセットされたデータは Global に使用できる。

今回使用したソースコードは下記リポジトリに push しています。
https://github.com/wattanx/nextjs-firebase-sample

2021/09/26 追記

Firebase v9 がリリースされたので、v9 用にソースコードを修正しました。

https://firebase.google.com/docs/web/modular-upgrade

GitHubで編集を提案

Discussion