firebase functionsのCallableでCORSエラーが出る

コベリンランチ

こんにちは、id:numanuma08です。Firebase functionsのCallableを使って関数を定義したとき、ドキュメントどおりに設定をしたのに呼び出し元WebページでCORSエラーが出ました。どうにか解消できたのでまとめます。

CORSのエラーが出る

firebase functions第2世代でonCallを使った関数呼び出しが利用可能です。内部的にはHttpsによる呼び出しですが認証関連など、よくありがちな処理をFirebaseに移譲してロジック部分の実装に集中可能な実装方法です。

firebase.google.com

const {onCall, HttpsError} = require("firebase-functions/v2/https");

exports.addmessage = onCall((request) => {
  // ...
});

Webページからの呼び出しにも対応し、Functionの呼び出し先と異なるドメインからでもアクセスできるようCORSの設定追記が可能です。

firebase.google.com

しかし今回、ドキュメントにあるようにonCallメソッドのパラメータで許可するドメインを設定していたにも関わらず、Webページから関数を呼び出すとCORSのエラーが出力されました。

// CORSを設定して関数を定義する
export const myFunction = onCall({
  cors: ["my-customdomain.co.jp"],
}, async (request) => {
// 関数の実装
});
// 以下のエラーが出力される
Access to fetch at 'https://asia-northeast1-xxx.cloudfunctions.net/myFunction' from origin 'https://my-customdomain.co.jp' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

対策

この問題についてGoogleやStackoverflowで検索をすると様々な情報が出ますが、第1世代と第2世代の情報が混在していたりでカオスです。また、Cloud FunctionsのCORSエラーは実際はCORSによるエラー以外のケースもあるようでさらに混迷を極めました。結論として、私の場合対策はonCallcorsに渡すドメイン名を文字列ではなく正規表現で表現したところ問題が解消しました。

export const myFunction = onCall({
-  cors: ["my-customdomain.co.jp"],
+ cors: [/my-customdomain\.co\.jp$/]
}, async (request) => {
// 関数の実装
});

同じ現象で困っている人がいたら試してみてください。またCloud Functionsの呼び出しでCORSのエラーが出る場合は

  • 呼び出す関数名を誤っている
  • 呼び出す関数のRegionが誤っている

等の場合があるそうです。ここからはそういった別の問題かどうかを切り分けるためにどのように調査したか調べました。

調査

CORSのエラーかどうかを調べる

CORS以外でもCORSのエラーが出るとのことで、問題の切り分けのためいったんCORSの設定を消して関数をデプロイしました。

export const myFunction = onCall({
-  cors: ["my-customdomain.co.jp"],
}, async (request) => {
// 関数の実装
});

その結果、エラーが解消したためCORSの設定由来のエラーであるとわかりました。

OPTIONSリクエストの内容をチェックする

ブラウザはドメインを超えてHttpリクエストを出すとき、HTTP OPTIONSリクエストを送ってリクエスト元が許可されたドメインかどうかをチェックします。

https://developer.mozilla.org/ja/docs/Web/HTTP/Methods/OPTIONS:cite:embeded

このリクエスト・レスポンスをチェックするとCORSの設定が正しいかどうかわかります。

  • CORSの設定が正しくない時のレスポンス
Access-Control-Allow-Headers:
content-type
Access-Control-Allow-Methods:
POST
Alt-Svc:
h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
Content-Length:
0
Content-Type:
text/html
Date:
Tue, 03 Oct 2023 01:40:21 GMT
Server:
Google Frontend
Vary:
Origin, Access-Control-Request-Headers
X-Cloud-Trace-Context:
  • CORSの設定が正しいときのレスポンス
Access-Control-Allow-Headers:
content-type

Access-Control-Allow-Methods:
POST
Access-Control-Allow-Origin:
https://my-customdomain.co.jp
Alt-Svc:
h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
Content-Length:
0
Content-Type:
text/html
Date:
Tue, 03 Oct 2023 01:56:51 GMT
Server:
Google Frontend
Vary:
Origin, Access-Control-Request-Headers
X-Cloud-Trace-Context:

違いはAccess-Control-Allow-Originの有無です。このキーに許可されたドメインが含まれていれば異なるドメイン間の呼び出しでも問題なくレスポンスを得られます。

まとめと今後

今回、onCallのパラメータに渡すCORSを文字列ではなく正規表現としたところ正しい状態となりました。これがFirebaseのバグなのか私が見落とした仕様なのかまだわかりません。Stackoverflowやfunctions-jsのリポジトリを検索しても同様の問題がヒットしないため、私特有の現象?という気もしています。

正規表現による許可となっているため、想定外のドメインからのアクセスが許可されていないかどうかも気になります。いちおう、チェックしたので大丈夫だと思いますが。

しばらく様子を見て、ライブラリのアップデート後などで情報がなければIssueのチケット化を検討します。