🦥

Firebase AuthenticationをEmulatorで動かす

2021/03/20に公開

概要

すでにたくさん記事があるから書く必要はないが...
最近やったことをすぐに忘れちゃうおじさんなので
後で振り返るように全部書き出しておく!

直近の案件では認証にFirebase Authenticationを使ってたけど
ローカル環境でも実際のサービスにアクセスして認証行っててもったいない感が強かったので
ローカル開発用のdocker-compose環境にemulatorを導入してみた

Firebase Authentiationのservice作成

docker-composeの書きっぷり

こんな感じ。firebase serviceの方みてくだされ

docker-compose.yml
version: '3.8'
services:
  nextjs:
    build:
      context: ./frontend
      dockerfile: ./docker/Dockerfile
    container_name: frontend
    volumes:
      - ./frontend:/usr/src/app
      - /usr/src/app/node_modules
    command: "yarn dev"
    ports:
      - 3000:3000
  firebase:
    build: frontend/docker/firebase
    container_name: firebase
    ports:
      - 4000:4000
      - 9099:9099
    volumes:
      - ./frontend:/opt/workspace
    working_dir: /opt/workspace
    command: "firebase emulators:start --import=./firebase/data --export-on-exit --project ucwork"
    tty: true

ポイントとしては以下

  1. 9099portが認証用
  2. 4000portが認証情報とかブラウザで見れる用
  3. emulatorの起動方法
    1. --project ucworkでGCP project idを指定
      1. なんかこれ指定しないとコンテナ起動時こけちゃう
    2. --import指定でローカルユーザーデータとかの永続化
      1. これしとくとコンテナ落としても登録したアカウント消えない
    3. 永続化ディレクトリはgit管理して中身のファイルはignoreしたいのでこのファイル追加
    frontend/firebase/data/.gitignore
    # Ignore every files this directory
    *
    !.gitignore
    

Dockerfileの書きっぷり

java環境が必要らしいのでこんな感じ

frontend/docker/firebase/Dockerfile
FROM node:15.9.0-alpine3.13

RUN apk update \
 && apk --no-cache add openjdk11-jre-headless    \
 && rm -rf /var/cache/apk/*

RUN npm install -g firebase-tools

firebase用の設定

firebase用の設定ファイル準備
これないとちゃんと動かないポイ

frontend/firebase.json
{
  "emulators": {
    "auth": {
      "port": 9099,
      "host": "0.0.0.0"
    },
    "ui": {
      "enabled": true,
      "host": "0.0.0.0",
      "port": 4000
    }
  }
}

検証

docker-compose up -d --buildでコンテナ作成&起動して
http://localhost:4000/にアクセスするとこんなemulatorが見えるはず

Emulatorにアクセスするフロントエンド

Firebaseの初期化

自分の環境はReact, Next.js, TypeScriptなのでこんな感じのファイル用意して初期化

ローカルのEmulatorだから実際のFirebase情報詰めたconfigとかいらないんじゃないの?
と思ったけど、これ入れて初期化しておかないと
apiKeyがないよって怒られるので設定しとく😫
(あの手この手で初期化やってみたけど抜け道を見つけられなかった😫

frontend/services/firebase.ts
import firebase from "firebase/app";
import "firebase/auth";

const config = {
  apiKey: process.env.FIREBASE_API_KEY,
  authDomain: process.env.FIREBASE_AUTH_DOMAIN,
  databeseURL: process.env.FIREBASE_DATABASE_URL,
  projectId: process.env.FIREBASE_PROJECT_ID,
  storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.FIREBASE_MESSEGING_SENDER_ID,
  appId: process.env.FIREBASE_APP_ID,
  measurementId: process.env.FIREBASE_MEASUREMENT_ID,
};

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

if (process.env.NODE_ENV !== "production") {
  const auth = firebase.auth();
  auth.useEmulator(process.env.FIREBASE_AUTH_EMULATOR_URL);
}

export default firebase;

envファイルに必要な情報

Firebaseのconsoleでゲットできる情報を

こんな感じで詰めておく

frontend/.env.local
FIREBASE_API_KEY=xxx
FIREBASE_AUTH_DOMAIN=xxx.firebaseapp.com
FIREBASE_PROJECT_ID=xxx
FIREBASE_STORAGE_BUCKET=xxx.appspot.com
FIREBASE_MESSEGING_SENDER_ID=xxx
FIREBASE_APP_ID=xxx
FIREBASE_MEASUREMENT_ID=xxx
FIREBASE_AUTH_EMULATOR_URL=http://localhost:9099

暗号化されてるとしてもGitHubにあげたくないので
アクセス制限されたGCSにファイルあげて、ローカル環境構築時にとってくるようにしてる。
(みんなenvファイルどう管理してるんだろ🧐

firebase使って認証

こんな感じでGoogleアカウント使ってログインする実装してみる
いらないこといっぱい書いてあるけど、ポイントはsignInのメソッドだけ

frontend/pages/index.tsx
import { FC, useContext, Suspense } from "react";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import firebase from "../services/firebase";
import styles from "../styles/Home.module.css";
import { AuthContext } from "../context/Auth";
import GoogleSignInButton from "../components/atoms/GoogleSignInButton";
import { Grid, CircularProgress } from "@material-ui/core";

const Home: FC = () => {
  const [t] = useTranslation(["index"]);

  const { state } = useContext(AuthContext);
  const { currentUser } = state;

  const signIn = async () => {
    const provider = new firebase.auth.GoogleAuthProvider();
    await firebase.auth().signInWithRedirect(provider);
  };

  return (
    <Grid container>
      <main className={styles.main}>
        <h1 className={styles.title}>{t("title")}</h1>
        {currentUser ? (
          <Link href="/about">
            <a>{t("buttons.about")}</a>
          </Link>
        ) : (
          <GoogleSignInButton onClick={signIn} />
        )}
      </main>
    </Grid>
  );
};

const HomeWrapper = () => (
  <Suspense fallback={<CircularProgress />}>
    <Home />
  </Suspense>
);
export default HomeWrapper;

検証

この状態でフロントエンドのhttp://localhost:3000/にアクセスする

GOOGLEでサインインボタンのクリックがsignInメソッドに繋がってる感じ

FooterにEmulatorモードですって親切に出てる

実際に押してみるとこんなの出てくる

Auto-generateで適当な情報突っ込んでsign in with google

http://localhost:4000/にアクセスしてみるとさっき登録したユーザー入ってる!

実際のFirebase Authenticationに入ってないので
ローカルのEmulatorだけがちゃんと動いてそうだぞー

コンテナ落として再度あげて、http://localhost:4000/にアクセスしてみると
ちゃんと登録してユーザー残ってる!いい感じ!

docker-compose down
Stopping frontend  ... done
Stopping storybook ... done
Stopping firebase  ... done
Removing frontend  ... done
Removing storybook ... done
Removing firebase  ... done
Removing network base-app_default

docker-compose up -d
Creating network "base-app_default" with the default driver
Creating firebase  ... done
Creating frontend  ... done
Creating storybook ... done

まとめ

最近仕事変わってGCPからAWSメインに変わりそうな雰囲気なので
あんまり使わなくなりそう・・・AWSだとcognitoとかになるのかな...何もわからん🥸

ローカルは無邪気に触りたいのでこういう外部サービスのemulatorがあるのは助かる🏄‍♂️

Discussion