Firebase Functions [onCall]を使った関数でCORSが発生してしまう

# 前提

Firebase Functionsで、あらかじめ、次のような関数を用意しています。

```typescript:server.ts
import * as functions from 'firebase-functions';
import { HttpsError } from 'firebase-functions/lib/providers/https';

export const exampleMethod = functions
  .region('asia-northeast1')
  .https.onCall(async (data, context) => {
    // hogehogeが空白でなければ中身をそのまま返し、空白ならエラーを投げる
    if (data.hogehoge !== '') {
        return {
            hogehoge: data.hogehoge
        }
    } else {
        throw new HttpsError('invalid-argument', 'hogehoge is required.')
    }
  });
```

Firebase HostingにデプロイしているWebサイトからであれば、誰でも利用できる関数として用意しており、
`onCall`を使って定義しています。

以上の関数を、クライアント側で、次のように呼び出します。

```typescript:client.ts
// firebaseの事前準備のコードは省略

const functions = firebase.app().functions('asia-northeast1');
const exampleMethod = functions.httpsCallable('exampleMethod');

const result = (await exampleMethod({hogehoge: 'さんぷる文字列'})) as any;
console.log(result);
```

すると、次のようなエラーが出てしましました。

![コンソールのエラーメッセージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84550/333b7ba7-e0e6-f0c3-3a86-22a0b1dade47.png)

```:エラーメッセージ
Access to fetch at 'https://asia-northeast1-example.cloudfunctions.net/exampleMethod' from origin 'https://example.firebaseapp.com' 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.
```

誰もが一度は経験する、CORS、Access-Control-Allow-Originです。

そして、たちが悪いのが、**localhostで実行している（[Cloud Functions エミュレータ](https://firebase.google.com/docs/functions/local-emulator?hl=ja)を使っている）ときは、エラーが出ず、正常に実行できる**のです。

# よくあるCORSの原因（今回は違う）

Web上で情報を探すと、よく見かける原因と解決策は以下の通り。

## 1. onRequestを使っている

Functionsの関数を定義する際に、[onCall](https://firebase.google.com/docs/functions/callable-reference?hl=ja)ではなく、[onRequest](https://firebase.google.com/docs/functions/http-events?hl=ja)を使っている場合、きちんとセッティングをしないと、CORSにひっかかってしまいます。

しかし、今回は`onCall`を使っているため該当しません。というか、そもそもCORSを考えなくて済むという点が、`onCall`のメリットの一つのはず。。。

参考：[FirebaseのCloud FunctionsでCORSが~とかAccess-Control-Allow-Originが~と言われたらこれ](https://qiita.com/qrusadorz/items/40234ac0b5c5c2315cad)

## 2. 関数の内部でエラーが発生している

ログを見ても、特にエラーが出ているようには見えなかった。そもそも、localhostでは正常に実行できている。

## 3. スペルミス・リージョンの指定間違い

これも今回は該当せず。

# 解決方法

Google Cloud Functionsのドキュメントに、[Managing Access via IAM](https://cloud.google.com/functions/docs/securing/managing-access-iam)というものがあり、これによると、

> As of January 15, 2020, HTTP functions require authentication by default. You can specify whether a function allows unauthenticated invocation at or after deployment.

訳すと、

> 2020 年 1 月 15 日以降に作成された新しい HTTP 関数のデフォルトでは、認証が必要になります。デプロイ時またはデプロイ後に関数が未認証の呼び出しを実行できるかどうか指定できます。

だそうです。（日本語版のドキュメントは、更新が遅れているのか、日付が違っていました。）

というわけで、以下の通り、権限を追加してみます。

【1】[Google Cloud ConsoleのCloud Functionsのページ](https://console.cloud.google.com/functions/list)へ移動

【2】権限を設定したい関数にチェックを入れる

![関数にチェック](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84550/f96d83ae-b995-3b2f-a0d2-5f29a1b56bf9.png)

【3】画面右上の「情報パネルを表示」をクリックすると、権限を設定できるパネルが表示されるので、「ADD MEMBER」をクリック

![ADD MEMBERをクリック](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84550/1e50ec30-3ffb-63e8-fd6e-2d9f6cb7ba63.png)

【4】新しいメンバーに「allUsers」を追加し、ロールに「Cloud Functions 起動元」を追加して、「保存」をクリック

![各項目を設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84550/f3050511-2194-f242-4701-0dee52bd9998.png)

【5】「ALLOW PUBLIC ACCESS」をクリック

![ALLOW PUBLIC ACCESSをクリック](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84550/e0e6f673-51ff-5d87-8a2e-e4f98753d00a.png)

以上で、権限が追加され、正常に関数が実行できるようになりました。

![権限が追加された](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/84550/6bab5fe1-a251-16bc-ed25-10607d7c15d8.png)

参考：[GitHub firebase/functions-samples : Add Cors to callable function #395](https://github.com/firebase/functions-samples/issues/395)

~~なお、ホスティングされたページ以外から関数にアクセスすると、次のようなレスポンスになる。~~

~~{"error":{"message":"Bad Request","status":"INVALID_ARGUMENT"}}~~

(2020/7/31追記　上記記述は誤りの為、取消)

# 備考

セキュリティ的に問題がある場合はお教えいただけると幸いです。