NginxでCORS対策をする
リモートワーカーになって迎えた最初の試練はリモートならではの問題ではなく、Nginxでした。 NginxはWebサーバーなのでそこまで複雑な設定は必要がないと思っていました。 Railsの頃はPassengerを使えばよかったし、Node.jsをメインに使うようになってからもネット上のスニペットを貼り付けておしまい。
CORSというものは私の認識ではもともとそこまで厄介なものではありませんでした。 Nginxでreverse proxyをすればそもそも発生しませんし、なにか起こったとしてもcorsミドルウェアを使えばいい。 現在私が担当しているOAuth2サーバーもそのような認識でした。
しかし、OAuth2サーバーでは外部のサイトと連携する必要があるので当然 認可したあとに発行するトークンを使ってアクセスしようとするとたちまちうまくいかなくなりました。 当然それまで使っていたcorsも適用してみましたがうまくいきません。 これまで浅い知識で対処していたCORSについてきちんと向き合う必要が出てきました。
corsについて
まずはミドルウェア側から見てみましょう。 READMEのEnable CORS for a Single Routeをもとにしたコードです。
var express = require('express');
var cors = require('cors');
var app = express();
app.get('/', cors(), function (req, res, next) {
res.send('hello world');
});
この実装でcurl
コマンドでHTTPヘッダを調べてみると:
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 11
ETag: W/"b-Kq5sNclPz7QV2+lfQIuc6R7oRu0"
Date: Sat, 10 Oct 2020 07:34:54 GMT
Connection: keep-alive
このようにAccess-Control-Allow-Origin
が付与されます。
この時点で私が勘違いしていたのは例えばAccess-Control-Allow-Methods
という許可するメソッドを明示する諸々のヘッダは付与されないということでした。
これまでは何も考えずcors()
とすればすべてうまくいっていたはずが、うまくいかずに苦しむことになります。
Nginxについて
検証のためにDockerコンテナでNginxで以下のようなコードを用意しました。
server {
listen 80;
listen [::]:80;
server_name _;
charset utf-8;
location / {
proxy_pass http://demo:3000;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
}
}
http://demo:3000
がExpress側のコンテナにつながっているので、この状態で再びcurl
してみました。
HTTP/1.1 200 OK
Server: nginx/1.19.2
Date: Sat, 10 Oct 2020 08:37:10 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 11
Connection: keep-alive
X-Powered-By: Express
Access-Control-Allow-Origin: *
ETag: W/"b-Kq5sNclPz7QV2+lfQIuc6R7oRu0"
すると、このように何故かAccess-Control-Allow-Origin
がbypassされてしまいました。
本来はここでこのヘッダが出てこなくなっていたので、そのヘッダを表示するためにproxy_set_header
を使いましょうという理解でした。
いずれにしても私のサーバーではcorsの設定とproxy_set_header
がうまく反映されなかったのでcorsを削除し、Nginx側でadd_header
を使いました。
add_header
をするとAccess-Control-Allow-Origin
が二重に表示されるエラーも出ましたが、Nginx側でヘッダを管理してしまえば少なくともExpressで設定したのに表示されずに悩むこともなくなりました。
とはいえ理想はExpress側で管理できるようにしたいので、あくまでも暫定的な修正ですけれども。
CORSのヘッダについて
Access-Control-Allow-Origin
というヘッダに*
(ワイルドカード)を指定すればひとまず問題解決かというと、そんなことはありませんでした。
というのも、今になって気づいたのはCookieを使用していたからかもしれません。
https://developer.mozilla.org/ja/docs/Web/HTTP/CORS#Credentialed_requests_and_wildcards
資格情報を含むリクエストに対するレスポンスの時、サーバーは
Access-Control-Allow-Origin
ヘッダーで “*
” ワイルドカードではなくオリジンを指定しなければなりません。
今回うまくいかなかった原因としては、この一文を見落としていたのも大きいです。 FireFoxが原因で動かないとかではなく、Chromiumブラウザでも同様のエラーが起きていました。 最終的には以下の4つのヘッダを個別に指定して解決しています。
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization";
これらのヘッダに関する解説はMDNの方を参照してもらうとして、単純にcors()
というオプションだけではうまくいかなかったこと。
curl
を使ってヘッダの情報が正しく表示されているかデバッグすること。
解決方法がわかった今だからこそ冷静にドキュメントを読む余裕も出ているのですが、一度どうやっても動かないと思い込んでしまうと同じ情報でも見えるものがずいぶん異なるものです。
まだ理解しきれていない部分もあるので分かり次第更新する予定ですが、Nginxを軽んじて痛い目にあうのが今回の教訓でした。