読者です 読者をやめる 読者になる 読者になる

らっちゃいブログ

日々の学びと気づきを発信するブログ

iOS Safari限定でAjaxリクエストが失敗してハマった話

スポンサーリンク

以前こういった記事を投稿しましたが、今回ハマったのも JWT トークンを使ったAjaxリクエストでした。

racchai.hatenablog.com

状況

簡潔に状況を説明すると、事前に取得しておいた JWT トークンを Authrorization ヘッダーに乗せてリクエストしたものの、なぜかデータが取得できないという現象に遭遇しました。それも iPhoneSafari でしか再現しないというめんどくさい状況でした。

CORS 問題

サーバーのアクセスログを確認すると、どうやらそもそもリクエストがサーバーに届いていないことが判明。そういえばサーバー側で CORS 関連のヘッダーを設定するのを忘れていたことに気付きました。

そういうわけで nginx に add_header 群を追記して再トライ。

add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Origin,Authorization,Accept,X-Requested-With";

結果:変わらず

プリフライトリクエスト問題

Webインスペクトを使って、iPhone側で実際に何が起きているのかちゃんと確認してみたところ、どうやらプリフライトリクエストに失敗していることがわかりました。X-Requested-With ヘッダーを付与しているために、事前に OPTIONS を送信するようになっているのです。

OPTIONS も許可はしていたものの、この設定のままでは OPTIONS リクエストに対して 405 で返してしまう模様。Nginx はハマりどこりが多い奥が深いです。

こちらの記事参考に、OPTIONS リクエストには 204 で返すように設定してみました。

Handling CORS with Nginx — osteel's blog

以上の結果がこちら。今度こそ大丈夫そう。

add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Origin,Authorization,Accept,X-Requested-With";

location / {
    if ($request_method = OPTIONS) {
        add_header Content-Length 0;
        return 204;
    }
}

結果:プリフライトリクエストは無事通過したものの、401 エラー発生

設定見直し

401 エラーということで、ようやくリクエストがサーバーに届くようになりました。でも Authorization ヘッダーは届いていないんでしょうか。

その後いろいろ調べていたら、こちらの記事にたどり着きました。

qiita.com

クレデンシャルを必要とするリクエストの場合、Access-Control-Allow-Originにワイルドカードは使えまえん。キチンと許可オリジンを指定しましょう。

オリジン情報乗せるのめんどくさくてワイルドカード指定してしまってました。

Access-Control-Allow-Credentials: true も付けましょう クレデンシャルを必要とする場合はこのヘッダがないとブラウザはレスポンスを捨ててしまうのでセットで付けておきましょう。

へー!いままで捨てられてた気はしないですが、そういう事情もあるらしいのでこれもつけておくことにしました。

add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "POST, GET, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Origin,Authorization,Accept,X-Requested-With";
add_header Access-Control-Allow-Credentials: true

location / {
    if ($request_method = OPTIONS) {
        add_header Content-Length 0;
        return 204;
    }
}

結果:変わらず 401 エラー

Authorization ヘッダー問題

設定見直しは意味がなかった(?)ご様子。そうなれば、実はもう Authorization ヘッダーは届いてるだろうと思い、アクセスログに Authorization ヘッダー値を出力してみました。

すると・・「Basic ********」なる文字列が記録されているではないですか。

あれれ、ベーシック認証??

そうなんです。

ここまで伏せてきましたが、実はエラーが起きていたのはステージング環境で、第三者からのアクセスを避けるためにベーシック認証を設定していました。JWT リクエストとベーシック認証のヘッダーはどちらも Authorization を使うため、どうやらベーシック認証で JWT トークンを上書きされてしまっていたようです。あちゃーです。

でもXHRだとブラウザがベーシック認証を自動で設定するなんてこと・・あるんです。そう、iOS Safari ならね。

完全に余計なことをしやがって状態ですが、仕方がないのでベーシック認証をはずしてみました。

結果:直った!

まとめ

ベーシック認証と JWT 認証(django-rest-framework-jwt)は相性が悪い!

いやーはまった。半日つぶれました。