Azure App Serviceで別ドメインからAPIを呼び出せるように「CORS」のレスポンスヘッダを付けてみた:Tech TIPS
Webアプリ向けのAPIを構築する際、ちょっと実装が面倒なのが「CORS(Cross-Origin Resource Sharing:オリジン間リソース共有)」ではないだろうか? Azure App ServiceでAPIを構築する際に利用できるCORSのレスポンスヘッダ送信方法を幾つか紹介する。
対象:Azure App Service on Linux
WebアプリのためのAPIを構築する際、ちょっと面倒な処理として「CORS(Cross-Origin Resource Sharing:オリジン間リソース共有)」を挙げたくなる人は多いのではないだろうか。
Webアプリを構成するJavaScriptから呼び出されるAPIでは、呼び出し元のオリジン(スキームとドメイン)とAPIのオリジンが異なる場合、「Access-Control-Allow-Origin」といったレスポンスヘッダを返さなければならない。これを怠ると、クライアント側のWebブラウザでエラーが発生し、Webアプリは意図通りに動作してくれない。
本Tech TIPSでは、CORSについてある程度知っているエンジニアを前提として、Azureの「App Service on Linux」でAPIを構築する際のCORSの実装方法を幾つか紹介する。いずれの方法もメリット/デメリットがあるので、開発/運用体制などと照らし合わせて選んでいただきたい。
以下では、Azure Front DoorやApplication Gatewayなどには頼らず、App Service単体で実装できる方法に限定している。また説明を簡単にするため、プリフライトリクエストについては触れていない。
方法その1――APIのプログラム内にCORSのレスポンスヘッダ送信を実装する
Web/APIサーバ上で実行できるプログラミング言語は、たいていHTTPレスポンスヘッダを追加する機能を持っている。それを利用してCORSのレスポンスヘッダをクライアントへ送信できる。
以下は、PHPで「Access-Control-Allow-Origin」レスポンスヘッダを返すプログラムの例である。
<?php
// <前略>
// ---------- 以下を挿入 ----------
// 許可するオリジンを連想配列のキーに記す
$allowed_origin = [
'https://example.jp' => true,
'https://www.example.jp' => true,
];
// Originリクエストヘッダによってレスポンスが変わることを示すためにVaryヘッダを追加
header('Vary: Origin');
// Originリクエストヘッダからオリジンを取得
$origin = (string)filter_input(INPUT_SERVER, 'HTTP_ORIGIN', FILTER_SANITIZE_URL);
// オリジンが許可リストに存在するかどうか確認
if (isset($allowed_origin[$origin])) {
// 許可したオリジンを指定しつつCORSのヘッダを送信
header("Access-Control-Allow-Origin: $origin");
}
// ---------- 挿入終わり ----------
// <後略>
上記リストでは、「https://example.jp」「https://www.example.jp」というオリジンからのリクエストの場合のみ、「Access-Control-Allow-Origin」レスポンスヘッダにそれらのオリジンを値として設定しつつ、送信している。
「Vary: Origin」というレスポンスヘッダも追加しているのは、「Origin」リクエストヘッダの内容に応じてレスポンスが変わることをキャッシュサーバなどに示すためだ(CORSで推奨されている)。
上記リストでVaryより先にAccess-Control-Allow-Originの方を送信してしまうと、「NOTICE: PHP message: PHP Warning: Cannot modify header information - headers already sent by ……」という警告が記録されるとともに、Varyが正しく送信されないことがあるので注意したい。また複数箇所でVaryに値を設定していると、いずれかの値が上書きされて正しく反映されないことがあるので注意が必要だ。
API構築をサポートするライブラリやフレームワークによっては、CORSのサポートが実装されていることもある。一般的にフレームワークなどの方が、プログラミング言語のネイティブな機能に比べて、簡単に扱えて高機能なことが多いので、仕様を確認してみるとよいだろう。
この方法は、APIのプログラムを担当している開発者にとって簡単で手軽だ。その一方で、運用担当者からはCORSのレスポンスヘッダをどこからどうやって送っているのか、分かりにくいという欠点がある。
方法その2――App Serviceの標準機能でCORSのレスポンスヘッダを送信する
App Serviceには、CORSのレスポンスヘッダを送信するための機能が標準装備されている。
●AzureポータルでCORSを設定するには
■操作手順
- Azureポータルで対象のApp Serviceのページを開く
- 左側のメニューで[API]−[CORS]を選択する
- 「許可される元のドメイン」欄に許可するオリジン(スキームとドメイン名)を記入する
- [保存]ボタンをクリックする
[保存]ボタンを押しても、なかなか設定が反映されない場合、支障がなければApp Serviceを再起動すると比較的早く設定が反映されるようだ。
●ARMテンプレートからデプロイするときにCORSを設定するには
ARM(Azure Resource Manager)テンプレートでもCORSは設定できる。以下にBicepの例を記す。
// <前略>
resource webApp 'Microsoft.Web/sites@2024-04-01' = {
name: '<サイト名>'
location: '<リージョン名>'
kind: 'app,linux' // App Service on Linux
properties: { /* <省略> */ }
}
resource webAppConfig 'Microsoft.Web/sites/config@2024-04-01' = {
parent: webApp
name: 'web'
properties: {
// ---------- 以下を挿入 ----------
cors: {
allowedOrigins: [ // 許可するオリジンを以下に記載
'https://example.jp'
'https://www.example.jp'
]
supportCredentials: false
}
// ---------- 挿入終わり ----------
// <省略>
}
}
// <後略>
●【注意】全てのコンテンツにCORSのレスポンスヘッダが付加される
上記のApp Service標準機能でCORSを設定すると、以後、そのApp Service上にある全コンテンツに対してCORSのレスポンスヘッダが付加される。つまりAPIに限らず、画像やテキストなどにも付いてしまうということだ。また、コンテンツによって付加を止めたり、許可するオリジンを変えたり、といったカスタマイズはできない。
その他、「Access-Control-Allow-Origin」「Access-Control-Allow-Credentials」以外のCORSのレスポンスヘッダは、この機能では付加できないようだ。前述の「Vary: Origin」も、筆者が試した限りでは付くことはなかった。
方法その3――NGINXでCORSのレスポンスヘッダを送信する
Tech TIPS「【Azure】App Service on Linux『だけ』でHTTPレスポンスヘッダを追加する方法(NGINX編)」で説明している方法で、CORSのレスポンスヘッダを付加することも可能だ。
まず、「map」ディレクティブでリクエスト元のオリジンを判定する。
# <前略>
# ---------- 以下を挿入 ----------
# $http_origin: Originリクエストヘッダの値
# $cors_origin: 許可されているオリジン、または空文字列。後で利用
map $http_origin $cors_origin {
default ""; # Originヘッダがなかったり、不許可のオリジンだったりしたなら空文字列
"~*^https://example\.jp$" $http_origin; # 許可するオリジンを正規表現で表している
"~*^https://www\.example\.jp$" $http_origin; # 同上
}
# ---------- 挿入終わり ----------
# <後略>
設定ファイル: /home/custom/etc/nginx/conf.d/mapping.conf
※NGINXのレファレンス: map
mapディレクティブはhttpコンテキストにしか記述できない。そのため、[/home/custom/etc/nginx/conf.d/mapping.conf]という設定ファイルに上記リストを記すことで、[/etc/nginx/nginx.conf]のhttpコンテキスト内でインクルードされるようにする。
上記リストにある変数「$http_origin」には、Originリクエストヘッダの値(スキーム+ドメイン)が格納されている。それを許可オリジンと比較し、一致すれば変数「$cors_origin」に代入する。不一致またはOriginヘッダがなかった場合は、空文字列を代入する。
次に、[/home/custom/etc/nginx/sites-available/default.conf]のserverコンテキスト内で、APIのパスを特定するために「location」コンテキストを挿入し、そこに「add_header」ディレクティブを追加してCORS関連のヘッダを付加する。
server {
# <前略>
# ---------- 以下を挿入 ----------
location ~ ^/api/v\d+/.*[^/]\.php$ {
add_header Access-Control-Allow-Origin $cors_origin;
add_header Vary "Origin"; # Originによってレスポンスが変わるので
# <必要な処理をここに記述>
}
# ---------- 挿入終わり ----------
# <後略>
}
設定ファイル: /home/custom/etc/nginx/sites-available/default.conf
※NGINXのレファレンス: add_header
上記リストでは、[/api/v1/customer1/sample.php]というようなパスのAPI呼び出しに応答する際、「Access-Control-Allow-Origin」「Vary」の各レスポンスヘッダを付加している。不許可のオリジンからの呼び出しの場合、$cors_originが空文字列になるため、Access-Control-Allow-Originレスポンスヘッダは付加されない。
上記の2つの設定ファイルを作成したら、[/home/startup.sh]を書き換えて、[/etc/nginx]にある設定ファイルをシンボリックリンクで置き換えるようにする(Tech TIPS「【Azure】App Service on LinuxのWebサーバ「NGINX」をカスタマイズする方法」参照)。
#!/usr/bin/env bash
# <前略>
# ---------- 以下を挿入 ----------
# NGINXの設定ファイルを差し替える
ln -nfs /home/custom/etc/nginx/conf.d/mapping.conf /etc/nginx/conf.d/mapping.conf
ln -snf /home/custom/etc/nginx/sites-available/default.conf /etc/nginx/sites-available/default
# 差し替えた設定ファイルを検証して、成功したらNGINXに再読み込み
nginx -t && nginx -s reload
# ---------- 挿入終わり ----------
# <後略>
設定ファイル: /home/startup.sh
これでApp Serviceを再起動すると、カスタマイズした設定ファイルがNGINXに反映される。以後、上記リストで指定したパスのAPIを呼び出すと、CORSのヘッダ付きのレスポンスが返されるはずだ。
この方法のメリットは、Web/APIサーバの運用担当側でCORSなどのレスポンスヘッダを集中管理できることだ(開発者の負担を減らすことにもなる)。また細かいカスタマイズもできる。一方、デメリットはNGINXの設定にある程度精通している必要があることだろう。
■関連リンク
- チュートリアル:Azure App Service で CORS を使用して RESTful API をホストする(Microsoft Learn)
- オリジン間リソース共有 (CORS)(MDN Web Docs)
Copyright© Digital Advantage Corp. All Rights Reserved.