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

6 min read読了の目安(約5700字

はじめに

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

環境

  • Next.js v10.1.3
  • Chakra UI
  • TypeScript v4.2.3

ステート管理は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/utils/firebase.ts
import 'firebase/auth';

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

if (!firebase.apps.length) {
  firebase.initializeApp(config);
}

export default firebase

NEXT_PUBLICをつけると、APIKeyはクライアント側で見えてしまうが大丈夫らしい。
ただし、セキュリティールールでしっかり対応する必要がある。
https://qiita.com/mtskhs/items/f2abba5f284dbb446e0f

/src/utils/AuthUtil.ts
import firebase from './firebase';
import 'firebase/auth';

const provider = new firebase.auth.GoogleAuthProvider();

export function login(): Promise<void> {
    return new Promise((resolve, reject) => {
        firebase
            .auth()
            .signInWithRedirect(provider)
            .then(() => {
                resolve();
            })
            .catch(error => reject(error));
    });
}

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

Context APIを用いてAuthProviderを作る

Context APIについて

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

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

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

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

AuthContextとProviderを作成する

/src/components/Auth.tsx
import firebase from '../utils/firebase';
import { FC, createContext, useEffect, useState } from 'react';

type AuthContextProps = {
  currentUser: firebase.User | null | undefined;
}
  
const AuthContext = createContext<AuthContextProps>({ currentUser: undefined });

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

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

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

import theme from '../theme'
import { AppProps } from 'next/app'
import { AuthProvider } from '../components/Auth'

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 '../utils/AuthUtil'

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 { useContext } from 'react'
import { AuthContext } from '../components/Auth'
import { LoginButton, LogoutButton } from '../components/Button'

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

export default Index

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

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

まとめ

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

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

今回使用したソースコードは下記リポジトリにpushしています。

https://github.com/wattanx/nextjs-firebase-sample