SafariでサードパーティCookieを取得するStorage Access APIを試してみた

# 概要
iOS13.4のSafariにおいて、サードパーティCookieが完全にブロックされるようになりました。
詳しくは下記をご確認ください。
https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/

iOSの 設定＞Safari＞サイト越えトラッキングを防ぐ をOFFにすると、サードパーティCookieの読み書きが可能になりますが、Safariのデフォルトの設定では、サイト越えトラッキングがONとなっており、サードパーティCookieはブロックされます。

ここでは、上記のwebkit.orgでサードパーティCookieを取得する方法（Option 2）として挙げられている Storage Access API を試してみます。
結論から言いますと、サイト越えトラッキングがONの状態で、サードパーティCookieを取得することが出来ました。（document.cookieのみだが）
2020年3月27日時点のiOS13.4 のSafariで検証しています。

# サンプルコード
ファイルを下記のように配置してください。
hoge.com
└parent.php
foo.com
├child.php
└first_party_cookie.php

## parent.php
iframe タグを設置するページになります。

```php:parent.php
<?php
setcookie("1st-party-from-server", "hoge.com", time() + 86400);
?>
<!DOCTYPE html>
<meta name="viewport" content="width=device-width,initial-scale=1">
<html>
  <head>
    <meta charset="UTF-8" />
    <title>iframe test</title>
  </head>
  <body>
    <div>parent.php</div>
    <ul>
      <li><a href="http://foo.com/first_party_cookie.php">first party cookie set in foo.com</a></li>
    </ul>
    <div>
        <iframe src="http://foo.com/child.php" width="100%" height="400">
        </iframe>
    </div>
  </body>
</html>
```

## child.php
parent.php のiframeのsrcに設置するページになります。

```php:child.php
<?php
setcookie("3rd-party-from-server", "foo.com", time() + 86400);
?>
<!DOCTYPE html>
<meta name="viewport" content="width=device-width,initial-scale=1">
<html>
  <head>
    <meta charset="UTF-8" />
    <title>iframe test</title>
  </head>
  <body>
    <div>child.php</div>
    <div>$_COOKIE:<pre><?=htmlspecialchars(print_r($_COOKIE, true), ENT_QUOTES)?></pre></div>
    <div><button onclick="callRequestStorageAccess()">callRequestStorageAccess</button></div>
    <div><button onclick="getCookieByUserAction()">getCookieByUserAction</button></div>
    <script>
        window.onload = function() {
            console.log("Cookie when loading:" + document.cookie);
 
            var promise = document.hasStorageAccess();
            promise.then(
                function (hasAccess) {
                    // Boolean hasAccess says whether the document has access or not.
                    console.log("hasStorageAccess when loading:" + hasAccess);
                }
            );
        }
 
        function callRequestStorageAccess() {
            var promise = document.requestStorageAccess();
            promise.then(
                function () {
                    console.log("Storage access was granted");
                },
                function () {
                    console.log("Storage access was denied");
                }
            );
        }
 
        function getCookieByUserAction() {
            console.log("Cookie when user action:" + document.cookie);
            var promise = document.hasStorageAccess();
            promise.then(
                function (hasAccess) {
                    // Boolean hasAccess says whether the document has access or not.
                    console.log("hasStorageAccess when user action:" + hasAccess);
                }
            );
        }
    </script>
  </body>
</html>
```

## first_party_cookie.php

foo.com のファーストパーティCookieをSetするために用意します。iframe内ではなくTOPフレームとして遷移する必要があります。

```php:first_party_cookie.php
<?php
setcookie("1st-party-from-server", "foo.com", time() + 86400);
?>
<!DOCTYPE html>
<meta name="viewport" content="width=device-width,initial-scale=1">
<html>
  <head>
    <meta charset="UTF-8" />
    <title>first-paty-cookie</title>
  </head>
  <body>
    <div>first_party_cookie.php</div>
    <div>
        $_COOKIE:<pre><?=htmlspecialchars(print_r($_COOKIE, true), ENT_QUOTES)?></pre>
    </div>
    <div><button onclick="history.back()">back</button></div> 
    <script>
        document.cookie = "1st-party-from-js=foo.com";
    </script>
    </body>
</html>
```

# 確認方法
実機のiPhoneとMacを接続して行いました。コンソールについてはMacのSafariのWebインスペクタを使用しました。
SafariのWebインスペクタの使用方法はここでは割愛します。

1. iPhoneのSafariにて、hoge.comの parent.php を開きます
![parent.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246278/a9c9f0a1-0d7c-901e-3ef0-6c8963241dd2.png)


2. first_party_cookie in foo.com のリンクを押して、foo.comに設置した、first_party_cookie.php へ遷移します。
リロードをして、Cookieがセットされたのを確認してください。
![first_party_cookie.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246278/46637282-7860-522d-c7e3-dd8138d90ce6.png)
Safariの特徴として、foo.comのファーストパーティCookieが存在しない場合、後に実行するrequestStorageAccess()が失敗してしまいます。そのため事前にSet-Cookieしています。
backボタンで戻ります。

3. parent.phpをリロードします。
コンソールに下記のように表示されます。
![console_copy1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246278/1ca84a2f-a2e2-29ab-0f98-41e595f7c92f.png)
Cookie when loading:の後ろに何もないので、ロード時のdocument.cookieがnullになっていることが確認できます。
先ほど確認した1st-party-from-serverなどのCookieが、サードパーティではブロックされているためです。
まだ、requestStorageAccess()をしていないため、hasStorageAccessはfalseとなっています。

4. callRequestStorageAccessボタンを押します。
下記のポップアップが表示されます。
![popup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246278/480c4c95-4f24-578b-f50e-032143bf7b30.png)

許可を押し、requestStorageAccess()が成功した場合はコンソールにStorage access was grantedと表示されます。
![console_copy2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246278/8596260f-f950-5c6d-7576-4e0f48fa8c4e.png)

5. getCookieByUserActionボタンを押します。
コンソールにfoo.comのfirst_party_cookie.phpでSetしたCookieが表示されます。
これでサードパーティのdocument.cookieが取得できました。
hasStorageAccess()の結果もtrueになりました。
![console_copy3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246278/b10a1dff-96d7-929c-8d31-14401cd6e918.png)

# 注意点
- 事前にファーストパーティでSet-Cookieする必要がある。
- ユーザジェスチャーで、requestStorageAccess()をcallする必要がある。
  - requestStorageAccess()をpromiseのcallbackのfunctionに配置した場合、ユーザジェスチャとみなされず、失敗してしまうので注意。
- ページをリロードした場合は、requestStorageAccess()で取得した許可がリセットされてしまう。
  - その場合は再度ユーザジェスチャによるrequestStorageAccess()をcallする必要がある。
  - 2回目はポップアップは表示されない。

# まとめ
めでたくdocument.cookieは取得できたのですが、child.php への通信を覗いてみたたところ、Cookieヘッダーは存在しませんでした。
そのためサーバ側での$_COOKIE での取得は不可能でした。
現在の実装がサーバ側でのCookie取得に依存している場合は、document.cookieへの移行はハードルが高そうです。
またユーザジェスチャーが必要である点、~~リロードするとリセットされる点は、移行を不可能にする要素になるかも~~しれません。
2024年時点では、CHIPSと組み合わせることで永続的なログインが可能になっています。下記の記事を参照してください。
https://qiita.com/reoito/items/357d0e8e63290200f0fe