🙈

Next.jsとFirebase Authenticationでメール・パスワード認証を実装

8 min read

この記事について

初めまして、みずねこです。

Next.js+TypeScript+firebaseの構成で、推しのTWICEミナに関連した「ポモドーロタイマー」を開発しています。

この記事では、Next.jsとFirebase Authenticationを使ったメール・パスワード認証機能の実装ログイン画面のデザインの解説をしていきます。
何か間違っている点などありましたらご指摘いただけると幸いです。🙏

解説すること

  • Firebaseのアプリ作成と初期設定
  • Firebase Authenticationで認証機能を実装
  • Materia-UIで認証画面のデザイン

参考にした記事

コラボレータ

TWICEミナ推しのぷっちょ

Firebaseのアプリ作成と初期設定

Firebaseの初期設定

まずは、Firebaseコンソールから新規アプリ「mitan timer dev」を作成します。
サイドバーの歯車アイコン/プロジェクト設定に移りSDK snipetを確認します。
詳しくは、公式ドキュメントに掲載されております。🙏

Next.jsアプリの起動

今回は、ぷっちょが作成したpomodoro-clockをforkして、グラフ、デザインなどの要素を追加してアプリ開発を行います。

まずは、forkしたpomodoro-clockを起動するために、先ほどな手順で作成したSDK snipetをroot/.env.localに貼り付けます。

NEXT_PUBLIC_FIREBASE_API_KEY=xxxxxxxxx
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=mitan-timer-dev.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=mitan-timer-dev
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=mitan-timer-dev.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=xxxxx
NEXT_PUBLIC_FIREBASE_APP_ID=xxxxxxxxxxxxxx
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=xxxxxxxxxxxx

また、各ページで認証処理が走るようにroot/next.config.jsにもSDK snipetの情報を登録します。

module.exports = {
    env: {
        NEXT_PUBLIC_FIREBASE_KEY: process.env.FIREBASE_KEY,
        NEXT_PUBLIC_FIREBASE_DOMAIN: process.env.FIREBASE_DOMAIN,
        NEXT_PUBLIC_FIREBASE_DATABASE: process.env.FIREBASE_DATABASE,
        NEXT_PUBLIC_FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
        NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
        NEXT_PUBLIC_FIREBASE_SENDER_ID: process.env.FIREBASE_SENDER_ID,
        NEXT_PUBLIC_FIREBASE_APPID: process.env.FIREBASE_APPID
    }
}

次に、Firebaseアプリ情報を初期化します。

root/lib/db.js
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/storage';
import 'firebase/auth'

let db;
let storage;
let auth;
try {
    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_MESSAGING_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);
    }
    db = firebase.firestore();
    storage = firebase.app().storage('gs://mitan-timer-dev.appspot.com');
    auth = firebase.auth()
} catch (error) {
    console.log(error);
}

export { db, storage, auth };

ここで、exportの書き方間違えると以下のエラーが出ますので注意してください(かなり個人的なミスですので汎用性は低いかと(´;ω;`))

SerializableError: Error serializing `.currentUser` returned from `getServerSideProps` in "/".
Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit this value.

次に、以下のコマンドを叩いてアプリが起動することを確認します。

npm i
npm run dev

Firebase Authenticationで認証機能を実装

次に、認証情報や他データを親ページから子ページに共有するためのProvider(AuthProvider)を作成します。authのonAuthStateChangedメソッドでuser情報を取得します(cookieに持たせる)。ちなみに、nookiesはcookieの補完ライブラリのような位置付けです。

src/auth/AuthProvider.tsx
import { createContext, useState, useEffect } from 'react';
import firebase from 'firebase';
import nookies from 'nookies'; //ssrでcookieを使う
import { auth } from '../../lib/db';

export const AuthContext = createContext<{ user: firebase.User | null }>({
  user: null,
});

export function AuthProvider({ children }: any) {
  const [user, setUser] = useState<firebase.User | null>(null);

  useEffect(() => {
    return auth.onAuthStateChanged(async (user) => {
      if (!user) {
        setUser(null);
        nookies.set(null, 'uid', '', { path: '/' });
      } else {
        const uid = user.uid;
        setUser(user);
        //cookieにuserid保存
        nookies.set(null, 'uid', uid, { path: '/' });
      }
    });
  }, []);

  return <AuthContext.Provider value={{ user }}>{children}</AuthContext.Provider>;
}

次に、<AuthProvider><Component>をラップすることで、全てのアクセス先ページで認証処理が走るようにします。

src/pages/_app.tsx
import React from 'react';
import { ThemeProvider } from '@material-ui/core/styles';
import { CssBaseline } from '@material-ui/core';
import { muiTheme } from 'util/theme';
import 'styles/globals.css';
import type { AppProps } from 'next/app';
import { AuthProvider } from 'auth/AuthProvider';

const App: React.FC<AppProps> = ({ Component, pageProps }) => {
  React.useEffect(() => {
    // Remove the server-side injected CSS.
    const jssStyles = document.querySelector('#jss-server-side');
    jssStyles?.parentElement?.removeChild(jssStyles);
  }, []);

  return (
    <ThemeProvider theme={muiTheme}>
      <AuthProvider>
        <CssBaseline />
        <Component {...pageProps} />
      </AuthProvider>
    </ThemeProvider>
  );
};

export default App;

実際に、Firebaseのサーバーにアクセスし、以下のページを見に行きます。

画面名 概要 github
ホーム画面 ユーザデータやグラフを見ることができる。アプリ内の各画面へのアクセスが可能 src/pages/index.tsx
ログイン画面 メール・パスワードを入力して認証できる。パスワードリセット機能あり。 src/pages/login.tsx
サインアップ画面 メール・パスワードを入力してユーザ登録する src/pages/signup.tsx
設定画面 タスクを操作する(CRUD) src/pages/setting.tsx
ポモドーロタイマー画面 設定時間に合わせたタスクを休憩画面と交互に実行する src/pages/work.tsx

リダイレクト認証

ここでは、アプリを起動してトップページ(index.tsx)にアクセスするとuser情報があるかをチェックします。user情報があればトップページに留まり他ページへの遷移も可能になりますが、なければログインページへ遷移します(リダイレクト認証)。遷移したいURLをベタ打ちしても遷移できない(リダイレクト認証が働く)ことを確認してください。

Materia-UIで認証画面のデザイン

次に、Firebase Authenticationのデフォルト設定ではログインページ・サインアップページのUIがかなり質素なので、
Material-UIとTWICEミナの画像を使ってUIを強化していきます。

material-uiの、templateからSing-in Sideを選択し、適宜オリジナルに編集すればOKです。
自分はパスワードリセットメール送信機能とTWICEミナの画像を反映させました。

ログイン画面

サインアップ画面

アプリを起動するたびにTWICEミナに会えるなんてなんて素晴らしいアプリなんでしょうね!

積み残し

material-ui/template/Sing-in Sideにおいて、画像領域に無料の画像提供サイトからランダムに拾ったファイルを格納し、ログイン画面アクセス時に毎度画像が切り替わる方式になっていました。

同じように、Webサイトからみーたん(TWICEミナ)の画像をスクレイピング後AWS s3Cloud Firestoreなどにプールしておき、ログイン画面アクセス時に画像格納先からランダムに取得するようにしておけば、毎回表情の違うみーたんに会えるという素晴らしいUI/UXを実現できそうですね。

基礎用語の習得

全体を通して、基礎用語はその都度調べながらScrapboxにまとめています。ご参考までに🙏

所感

当初、Google認証の実装を目標にreact-reduxを使ったのですが、知識に乏しく実装できそうになく、扱いやすいFirebase Authenticationに変更しました。

簡単に実装できそうでしたがSSRやcookieの実装知識が必要でした(コラボレータは神です)。

認証機能はWebアプリケーション開発には必須の機能ですから、開発体験(バックエンド・フロントエンド)としては満足しています。

最後までお読みいただきありがとうございました😊

今後やりたいこと

  • CloudFirestoreのデータグラフ化
  • コントリビューション
  • 忘却アラート
  • ユーザ登録時のフォーム(名前、アバター)
  • ユーザ一覧画面
  • 認証画面のランダム画像表示
  • ONCE用のタイムライン&チャット
  • 全体UIの修正
  • PWA対応
  • Twitterbot
  • 基礎用語の習得

Discussion

ログインするとコメントできます