😀

さくっとDockerを使ってFirebase エミュレータのAuthenticationで認証を試す

2024/07/05に公開

はじめに

株式会社バニッシュ・スタンダードでエンジニアをしております、chanponと申します。最近、Firebaseを触る機会があり、Firebaseエミュレータの構築について学びましたので、その経験を共有したいと思います。この記事では、Dockerを使ってFirebaseエミュレータを立ち上げ、Authenticationを利用したユーザーの登録から認証までの流れを紹介します。
https://firebase.google.com/docs/emulator-suite?hl=ja

事前準備

まず、FirebaseエミュレータをDockerで動かすために必要なファイルを準備します。プロジェクトIDはexampleとします。

ファイル構成

firebase
├── .firebaserc
├── firebase.json
├── compose.yml
└── Dockerfile

.firebaserc

プロジェクトIDを指定します。

{
  "projects": {
    "default": "example"
  }
}

firebase.json
使用するサービスを指定します。今回はAuthenticationとUIのみを使用します。

{
  "emulators": {
    "auth": {
      "host": "0.0.0.0",
      "port": 9099
    },
    "ui": {
      "host": "0.0.0.0",
      "port": 4000,
      "enabled": true
    },
    "singleProjectMode": true
  }
}

Dockerfile
Node.jsとFirebaseツールを含むDockerイメージを作成します。

FROM node:20.14.0-alpine3.20

# 必要なパッケージをインストール
RUN apk update \
 && apk --no-cache add openjdk17-jre-headless \
 && rm -rf /var/cache/apk/*

# Firebase CLIをインストール
RUN npm install -g firebase-tools

Docker Compose設定
前述のファイルをマウントしてDocker Composeを設定します。

services:
  firebase:
    container_name: 'firebase'
    build:
      context: ./
      dockerfile: Dockerfile
    volumes:
      - ./firebase/.firebaserc:/opt/firebase/.firebaserc
      - ./firebase/firebase.json:/opt/firebase/firebase.json
    ports:
      - 4000:4000
      - 9099:9099
    working_dir: /opt/firebase
    command: firebase emulators:start --project example
    tty: true

Firebaseエミュレータの立ち上げ

Docker Composeでエミュレータを立ち上げます。

docker compose up

エミュレータが立ち上がると、エミュレータの画面にアクセスできます。
エミュレータの管理画面にアクセスすると、以下のような画面が表示されます。

Firebaseエミュレータを使ったサンプルコード

以下にFirebase Admin Go SDKを使ったユーザー登録と認証の例を示します。

ユーザー登録
以下のコードは、定義した情報を使ってユーザーを登録します。

package main

import (
	"context"
	"log"
	"os"

	firebase "firebase.google.com/go/v4"
	"firebase.google.com/go/v4/auth"
)

const (
	projectID   = "example"
	email       = "test@example.com"
	password    = "12345678"
	uID         = "test"
	displayName = "test"
)

func main() {
	// Firebaseエミュレータのホストを設定
	os.Setenv("FIREBASE_AUTH_EMULATOR_HOST", "localhost:9099")

	// コンテキストの作成
	ctx := context.Background()

	// Firebaseアプリの初期化
	app, err := firebase.NewApp(ctx, &firebase.Config{
		ProjectID: projectID,
	})
	if err != nil {
		log.Fatalf("Firebaseアプリの初期化に失敗しました: %v", err)
	}

	// Firebase Authクライアントの作成
	client, err := app.Auth(ctx)
	if err != nil {
		log.Fatalf("Firebase Authクライアントの作成に失敗しました: %v", err)
	}

	// ユーザー情報の設定
	user := new(auth.UserToCreate).
		DisplayName(displayName).
		Email(email).
		Password(password).
		UID(uID)

	// ユーザーの作成
	record, err := client.CreateUser(ctx, user)
	if err != nil {
		log.Fatalf("ユーザーの作成に失敗しました: %v", err)
	}

	// 登録されたユーザー情報をログに出力
	log.Printf("ユーザーが作成されました: %+v", record)
}

登録が完了すると、FirebaseエミュレータのAuthentication画面に登録したユーザーが表示されます。

ログイン
上記で登録したユーザー情報を使ってログインします。
Firebaseエミュレータでもapikeyを指定する必要がありますので、今回はdummyを設定しています。

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"os"

	firebase "firebase.google.com/go/v4"
)

type Login struct {
	Email             string `json:"email"`
	Password          string `json:"password"`
	ReturnSecureToken bool   `json:"returnSecureToken"`
}

const (
	projectID = "example"
    // Firebaseエミュレータの場合、keyには任意の値を設定
	endpoint  = "http://localhost:9099/identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=dummy"
	email     = "test@example.com"
	password  = "12345678"
)

func main() {
	// Firebaseエミュレータのホストを設定
	os.Setenv("FIREBASE_AUTH_EMULATOR_HOST", "localhost:9099")

	// コンテキストの作成
	ctx := context.Background()

	// Firebaseアプリの初期化
	app, err := firebase.NewApp(ctx, &firebase.Config{
		ProjectID: projectID,
	})
	if err != nil {
		log.Fatalf("Firebaseアプリの初期化に失敗しました: %v", err)
	}

	// Firebase Authクライアントの作成
	client, err := app.Auth(ctx)
	if err != nil {
		log.Fatalf("Firebase Authクライアントの作成に失敗しました: %v", err)
	}

	// ログイン処理を実行しIDトークンを取得
	idToken, err := login(ctx, email, password)
	if err != nil {
		log.Fatalf("ログインに失敗しました: %v", err)
	}

	// IDトークンを検証
	token, err := client.VerifyIDToken(ctx, idToken)
	if err != nil {
		log.Fatalf("IDトークンの検証に失敗しました: %v", err)
	}

	// ログインユーザーのUIDをログに出力
	log.Println("ログイン成功: UID =", token.UID)
}

// ログイン処理を行う関数
func login(ctx context.Context, email, password string) (string, error) {
	// ログインリクエストのペイロードを作成
	payload, err := json.Marshal(&Login{
		Email:             email,
		Password:          password,
		ReturnSecureToken: true,
	})
	if err != nil {
		return "", err
	}

	// ログインリクエストを作成
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(payload))
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", "application/json")

	// リクエストを送信
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	// レスポンスのデコード
	var result map[string]interface{}
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
		return "", err
	}

	// IDトークンの取得
	idToken, ok := result["idToken"].(string)
	if !ok {
		return "", errors.New("IDトークンが見つかりません")
	}
	return idToken, nil
}

まとめ

Firebaseエミュレータを使うことで、ローカル環境で簡単にFirebaseの機能を試すことができます。特に、エミュレータをDockerで立ち上げることで、環境構築が容易になり、チームメンバー間での共有もスムーズに行えます。今回はAuthentication機能を使った基本的なユーザー登録と認証の流れを紹介しました。エミュレータを活用して、他のFirebase機能もぜひ試してみてください。

Discussion