Access-Control-Max-Age完全ガイド:CORSプリフライトの動作、設定例、セキュリティと最適化

Access-Control-Max-Ageとは何か

Access-Control-Max-Ageは、CORS(Cross-Origin Resource Sharing)におけるプリフライト応答に含まれるHTTPレスポンスヘッダで、クライアント側がプリフライトの結果(サーバが指定した許可情報)をどれくらいの間キャッシュできるかを秒数で指定します。プリフライトとは、実際のリクエストを発行する前にブラウザがサーバに対してOPTIONSメソッドで問い合わせを行い、実際に許可されているメソッドやカスタムヘッダなどを確認する仕組みです。Access-Control-Max-Ageはこのプリフライト問い合わせを削減し、不要なオーバーヘッドを減らす目的で利用されます。

基本仕様と書式

書式は単純で、レスポンスヘッダに整数の秒数を入れます。例えば86400は24時間を意味します。値は0以上の整数を取り、単位は秒です。Access-Control-Max-Ageはあくまでプリフライトの結果のキャッシュ期間を示すものであり、実際のレスポンスやリソースのキャッシュ制御とは別です。

プリフライトの流れとヘッダの関係

典型的なプリフライトの流れは以下の通りです。ブラウザがOriginヘッダを付けたOPTIONSリクエストを送る。サーバはAccess-Control-Allow-OriginやAccess-Control-Allow-Methods、Access-Control-Allow-Headersなどを返す。さらにAccess-Control-Max-Ageを返すと、ブラウザはその期間プリフライトを再実行せずに済ませることができます。したがってAccess-Control-Max-Ageはプリフライト応答とセットで機能します。プリフライトが成功したと判断されない場合は、このヘッダは無視されます。

実装例

サーバ側での設定方法はプラットフォームにより異なりますが、基本はレスポンスにAccess-Control-Max-Ageヘッダを追加するだけです。以下に代表的な設定例を示します。

  • Apacheの例: Header set Access-Control-Max-Age 86400
  • Nginxの例: add_header Access-Control-Max-Age 86400;
  • Express(Node.js)でcorsミドルウェア使用時の例: app.use(cors({ origin: 'https://example.com', maxAge: 86400 }))
  • Go net/httpの例: w.Header().Set('Access-Control-Max-Age','86400')

これらはプリフライトレスポンスに数値を付与する単純な方法です。注意点として、CORSの他のヘッダ(Access-Control-Allow-OriginやAccess-Control-Allow-Methods)が正しく設定されていることが前提です。

ブラウザの取り扱いと実用上の注意

仕様上は任意の秒数を指定できますが、ユーザーエージェント(ブラウザ)側で上限を設けることがあります。つまり非常に長い期間を指定してもブラウザが独自に短く切り詰める場合があるため、実運用では現実的な値を設定することが重要です。またプリフライト結果を長期間キャッシュすると、サーバ側の許可設定を変更してもクライアントが古い許可を使い続ける期間が発生します。これにより、設定変更の反映遅延やセキュリティ上のリスクが生じ得ます。

セキュリティ上の考慮点

Access-Control-Max-Age自体は情報漏洩を直接引き起こすものではありませんが、プリフライトキャッシュを長くすると、第三者が一度許可された操作を長時間にわたり行える点で運用上のリスクになります。たとえば誤ってワイルドカードでOriginを許可したり、不要なメソッドやヘッダを許可しているケースでは、プリフライトキャッシュによって被害が長引く可能性があります。そのため本番環境では必要最小限の許可と、適切なMax-Ageを設定することを推奨します。

よくある誤解と落とし穴

  • Access-Control-Max-Ageはレスポンスのキャッシュではない: 実際のリソース(GETやPOST)のキャッシュ制御はCache-ControlやExpiresで行います。Max-Ageはプリフライト結果だけを対象とします。
  • ヘッダがあるだけで動くわけではない: Access-Control-Allow-OriginやAllow-Methodsなど、プリフライトを成功させるための他のヘッダが正しく返っている必要があります。Originが一致しなければMax-Ageは無意味です。
  • ブラウザ間の違い: ユーザーエージェントによってMax-Ageの扱いや上限が異なることがあります。重要なポリシー変更を即時反映したい場合は短めのMax-Ageにするか、プリフライトが必須となる設計を見直す必要があります。

キャッシュとVaryヘッダ

プリフライト応答はOriginごとに異なる可能性が高いため、サーバ側でVary: Originを設定することが推奨されます。これによりプロキシやCDNがOriginごとに応答を区別してキャッシュし、誤ったOriginでの再利用を防げます。特に共有プロキシやCDNを経由するアプリケーションでは重要な措置です。

運用のベストプラクティス

  • 最小権限の原則を守る: Access-Control-Allow-OriginやAllow-Methodsは可能な限り限定し、ワイルドカードは避ける。
  • Max-Ageは用途に応じて調整: 開発や頻繁に設定変更が発生する環境では短め(数十秒〜数分)、安定運用のAPIでは数時間〜24時間程度を検討する。
  • Varyヘッダを設定してプロキシキャッシュの誤再利用を防ぐ。
  • 設定変更時の反映を確実にするために、重要な権限制御の変更後はクライアントへ通知するか短時間のMax-Ageへ切り替える運用を検討する。

デバッグと確認方法

プリフライトが実行されているか確認するには開発者ツールのネットワークタブでOPTIONSリクエストを見ます。あるいはコマンドラインで手動検証する場合はcurlを使うことができます。例: curl -i -X OPTIONS https://api.example.com/path -H Origin: https://example.com -H Access-Control-Request-Method: POST このレスポンスヘッダにAccess-Control-Max-Ageが含まれているか確認してください。ブラウザでの挙動を確認するには、同一Originからの連続したクロスオリジンリクエストを行い、最初のみOPTIONSが発生し後続は発生しないかを見ることでキャッシュが効いているか判断できます。

実際の設計判断例

内部APIで信頼できるクライアントのみがアクセスする場合は比較的長めのMax-Ageを設定してパフォーマンスを優先することができます。一方で外部提供APIや頻繁にアクセス制御を更新するAPIでは短めに設定して安全性と柔軟性を確保するのが良いでしょう。サービスの性質や運用体制に応じてバランスを取ることが重要です。

まとめ

Access-Control-Max-AgeはCORSプリフライトの効率化に有用なヘッダです。ただしユーザーエージェントごとの挙動差や、長期キャッシュによるポリシー反映遅延などの影響を理解したうえで、適切な値と関連ヘッダ(VaryやAllow系ヘッダ)を組み合わせて運用することが重要です。設定は簡単ですが、その運用設計がセキュリティとユーザ体験に直結します。

参考文献