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

KatsuYuzuのブログ

.NET の開発をメインとした日記です。

iOS7のMobile Safariでブラウザバック、フォワードした時にキャッシュを回避してリクエストさせる方法 #aspnetjp

Webアプリ作っててiPad mini(iOS7)のMobile Safariでハマった話。

前置き

今回のWebアプリは、ログイン機能があって、複数人で使うから最新情報が常に表示されていて欲しい(キャッシュからではなく)という要件がある。つまり、「いつでもサーバーにリクエストしてね☆ミ」ということ。一応ですが、iOS7の話です。

問題

iPadのSafariで現在表示しているページとhistory上で直前に表示していたページ(back、forward関わらず)の間の遷移がキャッシュから行われてしまう。
例えば、下記のような場合。

  • サインアウト→history.back→認証が必要なはずのページが見れてしまう
  • サインイン→history.back→サインイン→サインイン画面に埋め込んだワンタイムキー*1が不正
  • history.back、history.forwardの繰り返しの間は情報が最新ではない

最後のは、モバイル端末のためのそういう機能(back forward cache: bfcache)と言われればそれまでだけど、上2つは直したいなーと。

対策

response headerの指定とか、iframeを仕込んでiframe側を直前に表示していたページだと認識させる

とか、bfcacheの情報の樹海に飛び込んだりとか、色々したけどダメだった。

試行錯誤

そして、大体にしてこういう時って灯台モトクラシー。
office 365とかgoogleとかはてなとか、ソース見たり、response見たり、javascriptをgrepしたりしていて、トイレ逃げ込んだ時に何となく気づいた。
実は、ログイン機能があるにも関わらずlocalだからとHTTPで実験していた。おもむろにIISでHTTPSの構成をしてHTTPSで実験したら、すべての問題があっさり解決。

結論

少し実験したところ、下記の組み合わせでサーバーへリクエストするようになった。

  • 必須
    • HTTPS
  • いずれか
    • Cache-Control: no-store
    • Cache-Control: no-cache
    • Pragma: no-cache

iframe仕込む対策とかonunloadをフックとかresponse headerのExpireとかは何も影響しなかった。逆にHTTPではどうやってもリームーなのでpageshowでリロード。

実装

ASP.NET MVCであれば、しばやんさんのライブラリをnugetでインストールして、該当ActionにNoClientCacheAttributeを設定する*2

余談

サインインのActionにはValidateAntiForgeryTokenをつけることでCSRF対策が出来る。

ついでに、bfcache対策を済ませておくと通常操作でトークン不正になる場合がcookie無効にしてる時くらいになるので、アクションフィルターのHandleErrorAttributeでHttpAntiForgeryExceptionを引っかけると「ブラウザのcookieを有効にしてね☆ミ」って言えるようになる。

まとめ

開発環境は実行環境と揃える。身も蓋もない……

*1:ASP.NET MVCのHtmlHelper.AntiForgeryToken

*2:要件次第でグローバルフィルターにぶちこんでも可