🌵
Next.jsでFirebase Authenticationを使う(with Context API)
はじめに
自分が作ろうとしているサービスにログイン機能が必要である。
ログイン機能は 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 で新規プロジェクトを作成
以下コンソールより新規プロジェクトを作成する。
プロジェクト全般 -> 全般
より 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 コンポーネントのツリーに対して「グローバル」とみなすことができる、現在の認証済みユーザ・テーマ・優先言語といったデータを共有するために設計されています。
使い方はざっくりと以下の感じ
-
React.createContext(defaultValue)
で Context を作成する -
<Context.Provider value={data}></Context.Provider>
で値をセットする。 - 使う側は
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 はややこしいがざっくりこんな感じで使う
-
React.createContext(defaultValue)
で Context を作成する -
<Context.Provider value={data}></Context.Provider>
で値をセットする。 - 使う側は
useContext(1で作ったContext)
で 2 で設定したデータを取得する
Context にセットされたデータは Global に使用できる。
今回使用したソースコードは下記リポジトリに push しています。
2021/09/26 追記
Firebase v9 がリリースされたので、v9 用にソースコードを修正しました。
Discussion