🤞

クロスオリジンでLocalStorage等のデータをセキュアにやりとりする

1 min read

Webサービスのドメインを変更したい機会があり、Local Storage や IndexedDB に保存した情報(認証情報等を含む)をクロスオリジンでセキュアに移行する必要がありました。

小さな実装ですが気をつけることがちらほらあるので、具体的なシナリオを元に実装を紹介します。

シナリオ

LocalStorage の some-data というキーに格納されたデータを https://src.example.com から https://dest.example.com に移行するシナリオを考えます。

以下の図のような処理を実装します。

移行元の実装 (src)

まず、移行元では window.parent (=iframeの親)にデータを送ります。移行データだとわかるよう、data-transfer: prefix をつけて送信しています。
この時、Window#postMessage の第2引数で移行先のオリジンを指定してください。これを省くと第三者のWebサイトに iframe を埋め込まれ、Local Storage の情報が盗まれてしまいます。

if (window.parent == null || window.parent === window) {
  return;
}

const data = localStorage.getItem("some-data");
window.parent.postMessage(`data-transfer:${data}`, "https://dest.example.com");

移行先の実装 (dest)

移行先では、移行元ページを iframe で開きます。

<html lang="ja">
  <head>
    <script src="script.js" />
    <!-- 省略 -->
  </head>
  <body>
    <iframe src="https://src.example.com" style="display: none;" />
  </body>
</html>

そして、message イベントで iframe からのメッセージを拾います。そして data-transfer: prefix を抜いて LocalStorage に保存することで移行が完了します。
このとき、event.origin が移行元のオリジンであることを確認してください。これを省くと第三者のWebサイトから不正なデータを注入される恐れがあります。

script.js
const listener = (event) {
  if (event.origin !== "https://src.example.com" || !event.data.startsWith("data-transfer:")) {
    return;
  }
  // すでに移行済みの場合
  if (localStorage.getItem("some-data") != null) {
    return;
  }
  
  const data = event.data.slice("data-transfer:".length, event.data.length);
  
  localStorage.setItem("some-data", data);
}

window.addEventListener("message", listener);

おわりに

本当にセキュアなのか震えているのでマサカリがあればぜひお願いします。

Discussion

ログインするとコメントできます