Cloudflare WorkersでFirebase Authのトークン検証を実装する

今とあるアプリを新しく作っています。
構成はこんな感じです。
アプリ:Expo
APIサーバー:graphql-yoga on Cloudflare Workers
認証:Firebase Authentication
DB:Neon
このとき、APIをフルオープンにすると問題があるので制限を設けたいです。
ということで、サクッとfirebase-adminを使ってみたんですが、Cloudflare Workersでは動きませんでした。
firebase-adminを使って実装するとUncaught TypeError: globalThis.XMLHttpRequest is not a constructor
とエラーが出ます。
✘ [ERROR] A request to the Cloudflare API (/accounts/hogehoge/workers/scripts/fugafuga) failed.
Uncaught TypeError: globalThis.XMLHttpRequest is not a
constructor
at null.<anonymous>
(file:///Users/owner/Library/pnpm/global/5/.pnpm/[email protected]/node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/capability.js:20:11)
in checkTypeSupport
at null.<anonymous>
(file:///Users/owner/Library/pnpm/global/5/.pnpm/[email protected]/node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/capability.js:39:45)
in
../../../../../Library/pnpm/global/5/.pnpm/[email protected]/node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/capability.js
at null.<anonymous> (worker.js:9:58) in __init
at null.<anonymous>
(file:///Users/owner/Library/pnpm/global/5/.pnpm/[email protected]/node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/request.js:1:1)
in
../../../../../Library/pnpm/global/5/.pnpm/[email protected]/node_modules/rollup-plugin-node-polyfills/polyfills/http-lib/request.js
at null.<anonymous> (worker.js:9:58) in __init
at null.<anonymous> (node-modules-polyfills:http:30:1)
in node-modules-polyfills:http
at null.<anonymous> (worker.js:9:58) in __init
at null.<anonymous>
(node-modules-polyfills-commonjs:http:2:18) in
node-modules-polyfills-commonjs:http
at null.<anonymous> (worker.js:12:53) in __require
at null.<anonymous>
(file:///Users/owner/Projects/deg84/fugafuga/node_modules/firebase-admin/lib/utils/api-request.js:23:14)
in
../../node_modules/firebase-admin/lib/utils/api-request.js
[code: 10021]
If you think this is a bug, please open an issue at:
https://github.com/cloudflare/workers-sdk/issues/new/choose
なので、firebase-adminは使えません。
ちなみにgraphql-yogaには@graphql-yoga/plugin-jwtというプラグインがありますが、firebase-adminと同じようにエラーになってしまいます。
調べてみるとCode-Hex/firebase-auth-cloudflare-workersが使えそうでした。
Zennにも記事がありました。

なんですが、バージョンが2.0系になっていて若干情報が古くそのままでは動かなかったので、2024年3月末時点での状態を記録しておこうと思います。
前提
- Cloudflare Workersの設定はwrangler.tomlで行ってます
- ローカルで動かすときにもFirebaseのエミュレーターは使ってません。エミュレーターを使う場合は適宜
FIREBASE_AUTH_EMULATOR_HOST
の設定が必要です - Module Worker syntaxを使ってます(推奨なので)
wrangler.tomlの内容
main = "src/main.ts"
compatibility_date = "2024-03-26"
workers_dev = false
[env.dev]
name = "hoge-dev"
kv_namespaces = [
{ binding = "PUBLIC_JWK_CACHE_KV", id = "hogehogehoge" }
]
[env.dev.vars]
PROJECT_ID = "hoge-dev"
PUBLIC_JWK_CACHE_KEY = "hoge-dev-public-jwk-cache"
[env.staging]
name = "hoge-staging"
kv_namespaces = [
{ binding = "PUBLIC_JWK_CACHE_KV", id = "fugafugafuga" }
]
[env.staging.vars]
PROJECT_ID = "hoge-staging"
PUBLIC_JWK_CACHE_KEY = "hoge-staging-public-jwk-cache"
[env.production]
name = "hoge"
kv_namespaces = [
{ binding = "PUBLIC_JWK_CACHE_KV", id = "piyopiyopiyo" }
]
[env.production.vars]
PROJECT_ID = "hoge-prod"
PUBLIC_JWK_CACHE_KEY = "hoge-public-jwk-cache"
開発環境(ローカル)用の環境変数を[env.dev]
に書いてます。wrangler dev --env dev
で起動しないと読み込まれないので注意です。
Worker KVはそれぞれの環境用に作成してください。
main.ts
import type {
EmulatorEnv,
FirebaseIdToken,
} from "firebase-auth-cloudflare-workers";
import { Auth, WorkersKVStoreSingle } from "firebase-auth-cloudflare-workers";
import { createSchema, createYoga } from "graphql-yoga";
interface Bindings extends EmulatorEnv {
PROJECT_ID: string;
PUBLIC_JWK_CACHE_KEY: string;
PUBLIC_JWK_CACHE_KV: KVNamespace;
FIREBASE_AUTH_EMULATOR_HOST: string;
}
const verifyJWT = async (
req: Request,
env: Bindings,
): Promise<FirebaseIdToken | null> => {
const authorization = req.headers.get("Authorization");
if (authorization === null) {
return null;
}
const jwt = authorization.replace(/Bearer\s+/i, "");
const auth = Auth.getOrInitialize(
env.PROJECT_ID,
WorkersKVStoreSingle.getOrInitialize(
env.PUBLIC_JWK_CACHE_KEY,
env.PUBLIC_JWK_CACHE_KV,
),
);
return await auth.verifyIdToken(jwt, false, env);
};
const yoga = createYoga({
schema: createSchema({
typeDefs: /* GraphQL */ `
type Query {
hello: String!
}
`,
resolvers: {
Query: {
hello: (_, _args, context) => {
return `Hello, ${context.token?.name ?? "world"}!`;
},
},
},
}),
});
export async function fetch(
req: Request,
env: Bindings,
ctx: ExecutionContext,
) {
const token = await verifyJWT(req, env);
if (token === null) {
return new Response(null, {
status: 400,
});
}
return yoga(req, env, ctx, { token });
}
export default { fetch };
これで、リクエストのAuthorization
ヘッダーのトークンをチェックするようになります。
リクエスト例
curl -H "Content-Type: application/json" -H "Authorization: Bearer hogehoge" -d '{"query": "query { hello }"}' http://localhost:8787/graphql
トークンは適宜正しいもの変更してください。
まとめ
Cloudflare Workers上でFirebase Authenticationを使う方法について、firebase-auth-cloudflare-workersライブラリを利用した実装方法を紹介しました。
リクエストのAuthorizationヘッダーに含まれるJWTトークンを検証し、認証済みのリクエストのみ許可するようにできました。
同じように困っている方の参考になれば幸いです。
Cloudflare Workers便利なので使っていきましょう!