Firebase で何かを POST しようとしたら CORS の preflight で怒られましたね？ 


**DISCLAIMER:** 最後の「お世話になったサイト」を先に見られることをオススメします。


## CORS でありがちな ERROR

このエラーでググりました？今、なにか`POST`しようとしてますよね？

```
Access to XMLHttpRequest at 'http://localhost:5000/your-firebase-project-name/your-firebase-region/analyzeTweet' from origin 'http://localhost:3000' 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.
```



ここで、多くの人は「そんなことはない、おれのサーバサイドはちゃんと `Access-Control-Allow-Origin` を返すようにしている！」と思って `curl` で確認を始めますね。`-H "Origin: localhost:3000"` をつけるだけです。



## `curl` で CORS のヘッダを確認する

```
$ curl -H "Content-Type:application/json" -d '{"tweet":"i am happy"}' http://localhost:5000/your-firebase-project-name/your-firebase-region/analyzeTweet -vvv -H "Origin: localhost:3000"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (your-firebase-project-namewer-your-firebase-region-central1/analyzeTweet HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Origin: localhost:3000
> Content-Length: 22
> 
* upload completely sent off: 22 out of 22 bytes
< HTTP/1.1 200 OK
< x-powered-by: Express
< cache-control: no-cache, no-store, must-revalidate
< pragma: no-cache
< expires: 0
< access-control-allow-origin: localhost:3000
< vary: Origin
< content-type: application/json; charset=utf-8
< content-length: 57
< etag: W/"39-yPolt7ZZODo5n87HTQf8sBxIYwg"
< date: Tue, 30 Apr 2019 01:03:44 GMT
< connection: close
< 
* Closing connection 0
```



はい。`POST` でちゃんと `Origin: localhost:3000`  を渡していて、サーバサイドも `access-control-allow-origin: localhost:3000` と合言葉を返しています。問題なさそうです



でも、もともとのエラーは`Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.` です



##  Preflight?

- ある一定条件のリクエストを投げる際には事前に `preflight` という通信が発生するようです
- 詳細な発生条件は[ここ](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Preflighted_requests)にあります。いろんな条件がありますが、Ajaxなコールだとだいたい `content-type: application/json` なので、ここで `preflight` 対象となるんじゃないでしょうか
- ちなみに preflightの `Request Method:` は `OPTIONS` となるので、先の `curl` だと一見うまくいっているように見えて、実は再現できてません



## サーバサイドは普通はこれでいける

要はサーバサイドで preflight 含めて、正しくヘッダつけて返せばいいのです。やりかたはググればたくさんでてきます。例えば `expressjs/cors` を使えば、よきに計らってくれるはずです。（以下の `#1` , ` #2` を追加するだけ）

```javascript

// Use https://github.com/expressjs/cors ... #1
const cors = require('cors')({ origin: true });

exports.getTweets = functions.https.onRequest((request, response) => {
  
  const params = {"params-that-you-need": "x"};
  
  twitter_client.get('statuses/user_timeline.json', params, (error, tweets, res) => {
    if (!error) {
      // Add necessary headers automatically. Thanks expressjs/cors! ... #2
      cors(request, response, () => { 
        response.send(tweets);
      });
    }
  });

});
```



この設定で `curl` で preflight -  `Request Method: OPTIONS` してみると



```shell

$ curl -X OPTIONS -H "Content-Type:application/json" -d '{"twitter_screen_name":"your-twitter-name"}' http://localhost:5000/your-firebase-project-name/your-firebase-region/getTweets -H "Origin: http://localhost:3000" -vvv
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)your-firebase-project-name/your-firebase-region/getTweets HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Origin: http://localhost:3000
> Content-Length: 33
> 
* upload completely sent off: 33 out of 33 bytes
< HTTP/1.1 204 No Content
< x-powered-by: Express
< cache-control: no-cache, no-store, must-revalidate
< pragma: no-cache
< expires: 0
< access-control-allow-origin: http://localhost:3000
< vary: Origin, Access-Control-Request-Headers
< access-control-allow-methods: GET,HEAD,PUT,PATCH,POST,DELETE
< content-length: 0
< date: Tue, 30 Apr 2019 02:04:33 GMT
< connection: close
< 
* Closing connection 0
```



サーバからは `access-control-allow-origin` に加えて `access-control-allow-methods` で次に受け付けるメソッドが返ってきています。このケースではサーバ側のコードは preflightである `OPTIONS` とその後の `POST` を 意識せずに対応できています



## それでもうまくいかない場合

ただ、`curl` での `POST` は動くけど、ブラウザからの preflight だけどうしてもうまくいかな場合があったので、こんな感じで `OPTIONS` きたら必要なヘッダつけて `200 OK` 返す方法で凌いどいたぜ...

```javascript

const cors = require('cors')({ origin: true });

exports.analyzeTweet = functions.https.onRequest(async (request, response) => {

  if (request.method == "OPTIONS") {
      cors(request, response, () => {
        response.status(200).send()
      });
      return
  }

  // Logic for other than OPTIONS from here - eg. handling POST, etc.

});
```

あと、お世話になったサイトに挙げますが `onRequest` じゃなくて `onCall` 使う方が簡単でいいという話もあります

## お世話になったサイト

- [FirebaseのCloud FunctionsでCORSが~とかAccess-Control-Allow-Originが~と言われたらこれ](https://qiita.com/qrusadorz/items/40234ac0b5c5c2315cad)
- [CORS を分かってないから動くコード書いて理解する](https://qiita.com/mochizukikotaro/items/6b72ad595db8a6b5514f)
- [オリジン間リソース共有 (CORS)](https://developer.mozilla.org/ja/docs/Web/HTTP/CORS)

さて、これでやっと次にいけます。