WordPress REST APIとCORS

AMPプラグインのCORS 1
「がとらぼ」のAMPページはamp-listとWordPress標準のREST APIを使って「最近の投稿」リストを表示させるようにしていたのだが、いつのまにかGoogleのAMPキャッシュページではそのリストが表示されなくなっている。(上の画像の赤い破線の四角部分が空白になっている)
ローカルサーバのAMPページでは正しく表示されるので仕組みとしては正しい筈。また、CORSも解決済み(全許可)の筈なのに何故だ?。
(この半年ほどHTTPサーバのNginxの設定やテーマのfunctions.phpを弄り倒してるので自分でも何をどうしたかよく憶えていない。)

AMPプラグインのCORS 2
Chromeブラウザのデベロッパーツールでエラーを確認した。
HTMLヘッダのAccess-Control-Allow-Originの値を指摘されている。 https://gato-intaa-net.cdn.ampproject.orgと*の2つが指定されているのがダメよということ。
「*」 (全てのドメイン)は自分が指定したが、こんな複数指定に憶えがない。というかGoogleのAMPキャッシュのホストが何故勝手に指定されている?

「がとらぼ」のGoogle AMPキャッシュはホスト名(オリジン)が gato-intaa-net.cdn.ampproject.org なので、そのAMPキャッシュページを閲覧する状態を再現するためにそのオリジン指定を付けてREST APIで「がとらぼ」の「最近の投稿」のJSONを取得する。(下)
なお、CORSの確認ならオリジンは元ホスト以外なら正直何でも良い。

$ curl 'https://gato.intaa.net/wp-json/wp/v2/posts?__amp_source_origin=https%3A%2F%2Fgato.intaa.net' -I -H 'origin: https://gato-intaa-net.cdn.ampproject.org'
HTTP/2 200 
server: nginx
date: Tue, 12 Feb 2019 01:32:48 GMT
content-type: application/json; charset=UTF-8
amp-access-control-allow-source-origin: https://gato.intaa.net
x-robots-tag: noindex
x-content-type-options: nosniff
access-control-expose-headers: X-WP-Total, X-WP-TotalPages
access-control-allow-headers: Authorization, Content-Type
x-wp-total: 573
x-wp-totalpages: 58
link: <https://gato.intaa.net/wp-json/wp/v2/posts?page=2>; rel="next"
allow: GET
access-control-allow-origin: https://gato-intaa-net.cdn.ampproject.org
access-control-allow-methods: OPTIONS, GET, POST, PUT, PATCH, DELETE
access-control-allow-credentials: true
vary: Origin
strict-transport-security: max-age=31536000;
access-control-allow-origin: *
x-content-type-options: nosniff always
content-security-policy: default-src * 'self' data: 'unsafe-inline' 'unsafe-eval';

何故かaccess-control-allow-originヘッダが2つ出力されている。元々Nginxの設定で出力するようにしていたのが * の行でhttps://gato-intaa-net.cdn.ampproject.orgの行は知らない。
つまり、勝手にAccess-Control-Allow-Originヘッダが出力されている。
また、オリジンは何を指定してもその値がAccess-Control-Allow-Originヘッダに入るのでNginxの指定でいえば add_header Access-Control-Allow-Origin "$http_origin";と同じ状態らしい。

調べたところ、最近のWordPressで標準装備のREST APIを使うと勝手にHTTPレスポンスヘッダが3行ほど追加されるよう。

1
2
3
access-control-allow-origin: https://REST API呼び出し元ホスト(可変)
access-control-allow-methods: OPTIONS, GET, POST, PUT, PATCH, DELETE
access-control-allow-credentials: true

うーん、REST APIを使うときにCORSで困らないようにという親切なんだろうけど、勝手なことすんな!

ちなみにAMPプラグインのソースを見たところincludes/class-amp-http.phpにもHTTPレスポンスヘッダを付ける処理が追加されていた(v1.0から?)。こちらは何をするとそのヘッダが出力されるのかはまだ把握してないけど、いずれまた「勝手なことすんな!」と思う日が来るかも。

このAccess-Control-Allow-Originは勝手に追加出力(B)されると別に出力されている同ヘッダ(A)との組み合わせ(A且B)として判定されるので結果的に指定した値のどちらも許可されないという嫌らしいことになるみたい。

仕方がないのでNginxの設定で指定していた add_header access-control-allow-origin '*';の方を無効にしてみた。正直なところ「がとらぼ」はいろいろ掃除して外部からリソース共有(参照)されることもない筈なのでaccess-control-allow-originヘッダを付ける必要がなくなっていた。

いやいや、うちは、リソース提供するのでaccess-control-allow-originヘッダはHTTPサーバで常に出したいということであれば、WordPressのREST APIによって出力されるHTTPレスポンスヘッダの処理を上書きしてやるのが良さげ。

使用中のテーマのfunctions.phpの最後にでも追記
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//REST API HTTPレスポンスヘッダ制御上書き
add_action( 'rest_api_init', function() {
    remove_filter( 'rest_pre_serve_request', 'rest_send_cors_headers' );
    add_filter( 'rest_pre_serve_request', function( $value ) {
        //header( 'Access-Control-Allow-Origin: ' . $origin );
        header( 'Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE' );
        header( 'Access-Control-Allow-Credentials: true' );
        return $value;
    });
}, 15 );

5行目のコメントを外すと元と同じ動きになる筈。(そのときは上のコードは要らない筈だが。)

$ curl 'https://gato.intaa.net/wp-json/wp/v2/posts?__amp_source_origin=https%3A%2F%2Fgato.intaa.net' -I -H 'origin: https://gato-intaa-net.cdn.ampproject.org'
HTTP/2 200 
server: nginx
date: Tue, 12 Feb 2019 01:41:05 GMT
content-type: application/json; charset=UTF-8
amp-access-control-allow-source-origin: https://gato.intaa.net
x-robots-tag: noindex
x-content-type-options: nosniff
access-control-expose-headers: X-WP-Total, X-WP-TotalPages
access-control-allow-headers: Authorization, Content-Type
x-wp-total: 573
x-wp-totalpages: 58
link: <https://gato.intaa.net/wp-json/wp/v2/posts?page=2>; rel="next"
allow: GET
access-control-allow-origin: https://gato-intaa-net.cdn.ampproject.org
access-control-allow-methods: OPTIONS, GET, POST, PUT, PATCH, DELETE
access-control-allow-credentials: true
vary: Origin
strict-transport-security: max-age=31536000;
x-content-type-options: nosniff always
content-security-policy: default-src * 'self' data: 'unsafe-inline' 'unsafe-eval';

access-control-allow-originヘッダは1行だけになった。

AMPプラグインのCORS 3
「最新の投稿」のリスト自体はAMPキャッシュではない(そのためのamp-list + REST API化)ので結果はAMPページの再キャッシュ化を待つまでもなくすぐに確認できる。無事にリストが表示できるようになった。

対処は簡単だったけど、CORSは面倒で油断できない。