CORS地獄からの脱出:APIゲートウェイで「正しい」クロスオリジン制御を実装する
ネットワークの現場で最も「泣かされる」問題の一つが、このCORS(Cross-Origin Resource Sharing)だ。ブラウザのコンソールに吐き出される真っ赤なエラーメッセージを前に、とりあえず Access-Control-Allow-Origin: * と書いて寝てしまう――そんな経験はないだろうか。
しかし、APIゲートウェイという「門番」を運用する立場になれば、話はそう単純ではない。今回は、RFC 6454(Web Origin Concept)の理想と、現場で遭遇する泥臭い現実の狭間で、いかにして美しくセキュアなCORS制御を実装するか、その深淵を覗いてみよう。
—
1. なぜ「プリフライト」は必要なのか?
CORSは、ブラウザが勝手にクロスオリジンのリクエストを送るのを防ぐためのセキュリティ機構だ。特に、GETやPOSTのような単純なリクエストとは異なり、カスタムヘッダーを含んだりPUT/DELETEメソッドを使ったりする場合、ブラウザは本番通信の前に、まず OPTIONS メソッドで「ねえ、このオリジンからこんなことしてもいい?」と確認を行う。これがプリフライトリクエストだ。
APIゲートウェイでこの処理をサボると、クライアント側で「CORS error」が発生し、通信すら始まらない。この「通信の可否を問う」というステップこそが、現代のWebセキュリティの要なのだ。
—
2. APIゲートウェイにおける設計の鉄則
多くのエンジニアが陥る罠が、すべてのリクエストに対して固定のヘッダーを返す設計だ。しかし、Web APIは多様なクライアントからアクセスされる。「動的なオリジン検証」こそが、プロフェッショナルの仕事である。
推奨される実装フロー
1. リクエスト受信: APIゲートウェイが OPTIONS リクエストを受け取る。
2. ホワイトリスト検証: Origin ヘッダーの内容を読み取り、許可されたドメインリストと照合する。
3. ヘッダー構築: 許可されたオリジンなら、その値を Access-Control-Allow-Origin にセットして返す。
4. 許可メソッドの提示: Access-Control-Allow-Methods で、実際に許可するメソッド(GET, POSTなど)を返す。
—
3. 実践:NginxをAPIゲートウェイに見立てた設定例
現場ではNginxをリバースプロキシ兼APIゲートウェイとして配置することが多いだろう。以下の設定は、動的にオリジンを判定する堅牢なパターンの雛形だ。
# 許可するドメインをmapで管理することで、IF文の連鎖を避ける
map $http_origin $cors_origin {
default "";
"https://app.example.com" "$http_origin";
"https://admin.example.com" "$http_origin";
}
server {
listen 443 ssl;
location /api/ {
# OPTIONSリクエスト(プリフライト)のハンドリング
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
add_header 'Access-Control-Max-Age' 86400; # 24時間は再確認をスキップ
return 204; # 本番通信の前に空の成功レスポンスを返す
}
# 通常リクエストへのヘッダー付与
add_header 'Access-Control-Allow-Origin' $cors_origin always;
proxy_pass http://backend_upstream;
}
}
この設定の肝は Access-Control-Max-Age だ。これを設定しないと、クライアントは毎回プリフライトを飛ばすことになり、レイテンシに直撃する。ネットワークスペシャリストとしては、この「無駄なパケットを減らす」視点を忘れてはならない。
—
4. デバッグの現場:curlで「手動プリフライト」を叩く
ブラウザ越しでは状況が見えないことが多い。そんな時は、迷わず curl で直接ゲートウェイを叩いてみることだ。
# プリフライトの挙動を確認するコマンド
curl -v -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
https://api.example.com/api/data
このコマンドのレスポンスを見て、Access-Control-Allow-Origin が期待通りに返ってきているかを確認する。もしここで何も返ってこないなら、ゲートウェイの設定か、あるいは途中のWAFがリクエストを遮断している可能性を疑うべきだ。
—
5. 最後に:なぜ「動的」であるべきなのか
たまに「面倒だから * でいいじゃん」という意見を聞くが、これは認証(CookieやAuthorizationヘッダー)を伴うリクエストでは動作しない。Access-Control-Allow-Credentials: true を併用する場合、Access-Control-Allow-Origin は * にできないという仕様があるからだ。
セキュリティと利便性のバランスを取るためには、今回紹介したような「許可ドメインの動的判定」が、スケーラブルなAPI設計における正解となる。
ネットワークは嘘をつかない。パケットのヘッダーの一つひとつに、設計者の意図が刻まれている。もし次に CORS エラーで立ち止まったら、ブラウザの向こう側で何が起きているのか、RFCとパケットのシーケンスを想像してみてほしい。それが、一流のエンジニアへの近道だ。
コメント