Tany's Blog

Hono + Cloudflare Workers で Firebase Authentication を利用するときにハマったこと


最近少しだけ時間が空いたので、去年頃から話題になっていたHonoを使ってみたかったので、勉強がてら使ってみることにしました。 デプロイ先は、これまた前々から興味があったCloudflare Workersに行うことにしました。 もともと Hono の出自的にも Cloudflare Workers 上で動かすことを想定されて作られたライブラリとのことなので、せっかくならまとめて使ってみようという試みです。

上記組み合わせに加えて、API の認証には Firebase Authentication を利用することにしたのですが、その時に少しだけハマりポイントがあったのでその点をまとめてみました。 ほとんど自分の知識不足からくるものではありましたが、同じような記事は見つからなかったので他の方の参考になればと、自分がハマったポイントをまとめてみました。(自分の調査メモの供養も兼ねて。)

開発環境

利用したライブラリ

  • Cloudflare Wrangler 3.105.1
  • Hono 4.6.17
  • @hono/firebase-auth 1.4.2(Hono で利用できる Firebase Authentication 用の Middleware ライブラリ)

前提条件

  • 今回の記事のスコープは、サーバーサイドの API 認証で利用する Firebase Authenticationのみです。
  • ユーザーのサインアップやサインインは、別途フロントエンドのアプリケーションを利用して Javascript 用の SDK を利用して実装していますが、その部分の詳細については今回の記事では扱いません。
  • API 認証の際には Session Cookie を利用して認証を行いました。

Hono の Middleware として利用する

Firebase Authentication を Middleware として利用できる

これは、先に調べておけばよかっただけの話なのですが、Firebase Authentication を利用する際には、Hono 側で Middleware を利用する形で組み込めるようになっています。 Firebase Authentication 以外にも例えば、GraphQL だったり、Validation 用の Zod といった TypeScript で開発する上で欠かせないライブラリを簡単に組み込めるようになっています。Third-party Middleware Firebase Authentication の Middleware は、hono/firebase-auth というライブラリで提供されています。

自分は初手でこれに気づかずに、Firebase Admin SDK を利用して認証を実装していたのですが、あとからそのことに気づき実装し直しました。 Hono の Middleware を利用することで認証を実装する際に、アプリケーション側では Firebase Admin SDK を利用する必要がなくなるので、コードがシンプルになるというメリットがあります。

KV の設定が必要

一点だけ上の README だけでは理解できなかったこととして、この Middleware を使うためには、Cloudflare Workers の KV を利用する(KV を利用しない場合は別の Key Value Store の機能を持ったサービスを利用する)必要があるようです。 設定自体は非常にシンプルで、下記のように wrangler.toml に設定を追加するだけなのですが、この設定自体が README だけではわからない(自分が見落としていたらすみません。)ため、初期化のエラーに悩まされました。

# Specify cache key to store and get public jwk.
PUBLIC_JWK_CACHE_KEY = "public-jwk-cache-key"

[[kv_namespaces]]
binding = "PUBLIC_JWK_CACHE_KV"
id = ""
preview_id = "testingId"

利用できる API は限定的

Hono 側で Middleware として利用する場合、2025/02/01 現在では基本的に JWT や Session Cookie からトークンを Verify するシンプルな機能を提供しています。 その他の Admin SDK が提供している API を利用するためには、別途 Cloudflare Workers 用のライブラリとして提供されている firebase-auth-cloudflare-workersを利用することによって、動作させることができます。 ちなみにこのライブラリは、Hono の Middleware の内部で呼ばれているライブラリです。

例えば、イニシャライズしたインスタンスを取得したり、ユーザーを取得するといった Admin SDK が提供している API を利用する場合には、上記のライブラリを利用することによって、動作させることができますが、すべての API がカバーされているわけではないので、用途によっては、Firebase Admin SDK を利用した方がよい場合もあります。(ただし罠がありますので、後述します。)のでその点は注意が必要です。

Firebase Admin SDK の機能の一部が Cloudflare Workers で利用できない

これも事前に調べておけばよかったのですが、Firebase Admin SDK は Cloudflare Workers では完全には利用できない場合があるようです。 Firebase Authentication の Node SDK は基本的には、Node.js のランタイムで動作させることを期待して作らているライブラリであるため、Cloudflare Workers では一部の機能が動かないケースがありました。

自分が遭遇したのは、作成されたユーザーを取得する際の getAuth().getUser(uid) です。 SDK の内部的にcrypto.createSign() を利用しているようで、これが Cloudflare Workers ではサポートされていない?ためにエラーが発生していました。

getAuth()
  .getUser(uid)
  .then((userRecord) => {
    // See the UserRecord reference doc for the contents of userRecord.
    console.log(`Successfully fetched user data: ${userRecord.toJSON()}`);
  })
  .catch((error) => {
    console.log('Error fetching user data:', error);
  });

firebase のアプリケーションのイニシャライズ(initializeApp)は、Cloudflare Workers でも動くようですが、その後に getAuth を呼び出すと上記のようなエラーが発生するため、理解するのに時間がかかりました。 上記で紹介した Cloudflare Workers 用の Firebase Authentication 用のライブラリを利用することでこれを回避することができますが、もしかしたら他にも方法があるかもしれません。前述の通りこのライブラリにはユーザーの削除等の API は提供されていないようなので、Admin SDK で提供されていない API に関しては直接 API を Call することによって回避する以外の方法があるのかもしれませんが、自分は見つけることができませんでした。

Firebase Authentication を利用した API 認証の流れを把握できておらず、サンプルコードの実装が理解できなかった

これも恥ずかしい話ですが、そもそも API 認証を行うにあたって、どのような流れで認証を行うかを把握できていなかったので、実装をどのように進めたらいいのかがわからず、時間を使ってしまいました。 Firebase Authentication は、もともと Frontend 側で利用したことはありましたが、サーバーサイドで利用するのは初めてだったのでそれを組み合わせた時にどう関連し合うのかのイメージが沸かず、実装以前の問題でした。

サンプルコードや、他の実装例を参考にしながらようやく自分なりに理解できたので、その流れをまとめてみます。 最終的には、以下の図のような流れで実装しました。

API認証の流れ

  1. ユーザーがフロントエンドのアプリケーションを利用してサインイン or サインアップする。(このとき Firebase の JavaScript SDK を利用して、ログイン機能を実装しています。)
  2. サインイン or サインアップが完了すると、Firebase の Auth User が取得できる。
  3. そのユーザーから JWT トークンを取得し、そのトークンと CSRF トークンをサーバーに送信する。
  4. JWT トークンを受け取ったサーバーでは、そのトークンと CSRF トークンを検証する。
  5. Google のサービスアカウントを利用して、Firebase Admin 用のクライアントを作成する。
  6. そのクライアントとユーザーの JWT トークンを利用して、Session Cookie を作成し、フロントエンドのアプリケーションにレスポンスを返す。

このような流れで、API 認証を行うことができます。 詳細についてはここでは説明しませんが、サーバー側での API 認証の一つの例として今回理解しました。 今思えば当たり前ではありますが、とりあえずドキュメントを読んでその通りに実装すれば形になるだろうと高をくくってしまい、結果として実装に時間がかかってしまったというのが自分のハマりポイントでした。

この流れさえ理解できると、あとはほとんどの実装はドキュメントに従って実装できます。 自分が実装時に参考にしたのは、hono/firebase-auth の README です。

サンプルコードでは、ユーザーのサインインの実装はサーバーサイド側に実装されていますが(html を返している。実際のコードはここ) 以下のコードでは、上記の 1 から 3 までのフローを満たしています。

app.get('/login')...

以下のコード部分が 4 から 6 までのフローを実装した部分です。

app.post('/login_session')...

それぞれを参考に実装すると、上記のフローを実現できます。

一点だけ注意が必要なのは、Firebase Admin 用のクライアントを作成する際に、自分は ServiceAccountCredential を利用しているので、そのサービスアカウントの JSON ファイルを環境変数として設定しておく必要があります。 最初、Cloudflare Wrangler の設定ファイルにその JSON ファイルを設定しておきました

[vars]
SERVICE_ACCOUNT_JSON = "..."

ただ、実際にはなぜかうまく JSON パースされなかったため、必要な値だけを環境変数で管理してそれらをコード内で組み立てるようにしました。 その際に、privateKey だけは改行コード(\n)が含まれているため、そのままでは JSON パースでエラーになってしまうので、その点だけ注意が必要です。 自分は、ServiceAccountCredential のコンストラクタに渡す際に、replace メソッドを利用して改行コードを実際の改行コードに変換しています。

const serviceAccount = {
  projectId: c.env.FIREBASE_PROJECT_ID,
  // replace \n with actual newline characters
  privateKey: c.env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, '\n'),
  clientEmail: c.env.FIREBASE_CLIENT_EMAIL,
};

const serviceAccountJSON = JSON.stringify(serviceAccount, null, 2);

const auth = AdminAuthApiClient.getOrInitialize(
  c.env.FIREBASE_PROJECT_ID,
  new ServiceAccountCredential(serviceAccountJSON)
);

参考

  • Hono 開発用ドキュメントが整っていて、サンプルコードも豊富で、実装の参考になりました。
  • firebase-auth-cloudflare-workers このライブラリ自体も TypeScript で実装されているので、中身で何をしているのかコールドリーディングできるので、詰まった時に読んでみるのもおすすめです。
  • Cloudflare Workers + KV + hono で簡単な API サーバを作る 全体的な Hono を使った API サーバの実装の参考になりました。