QRの流入元は、Cookieに預けない — 注文フォームのセッション設計とSafari対策

前回、チラシや自販機のQRコードからWeb注文につなげる仕組みの話を書きました。今回はその裏側、ログイン状態とQR流入元をどう保持しているかという技術寄りの話です。

きっかけは、二つの疑問でした。

  • 「Safariだと、Cookieは7日で消えるらしい」
  • 「プライベートブラウズで開かれたら、どこから来たお客様か分からなくなるのでは」

どちらも、放っておくと計測が丸ごと無意味になる話です。順番に整理します。

1. Cookieはどこに保存されるのか

まず前提から。Cookieはサーバーではなく、**お客様のスマートフォン(ブラウザ)**に保存されます。

【サーバー】                    【お客様のスマホ】

 注文フォーム ──認証成功──→   Cookie を発行・保存
                                   ↑ここに置かれる

 次回アクセス時
 注文フォーム ←──Cookie送信──   スマホが自動で提示
 「この人は誰か」をDBで照合

スタンプカードに近いイメージです。店側はカードを渡すだけで、持っているのはお客様。次に来たときにカードを見せてもらって、こちらの台帳(DB)と照合する。

2. 使っているCookieは2つだけ

Cookie名役割有効期間
porder_sessログイン状態の維持30日
porder_qr_src注文のQR流入元を記録30日

ログインの流れはこうです。

初回 メールアドレスを入力 → 6桁コードをメール送信 → コード確認 → スマホにCookieを渡す(30日有効)

2回目以降(30日以内) URLを開く → スマホが自動でCookieを提示 → そのまま注文画面へ(コード入力なし)

30日経過後 Cookieが期限切れ → もう一度6桁コードで認証

3.「Cookieは7日で消える」は、半分正しい

これはSafariのITP(Intelligent Tracking Prevention/追跡防止機能)の話です。ただし、すべてのCookieが7日になるわけではありません。線引きはここです。

7日で強制削除される

  • JavaScriptで書き込んだCookie(document.cookie = "..."
  • localStorage などのスクリプトから書けるストレージ

7日制限の対象外

  • サーバーが Set-Cookie ヘッダーで発行した、ファーストパーティのCookie
  • 有効期限はサーバーが指定した日数がそのまま効く

このシステムのCookieは httpOnly: true でサーバーが発行しているので、後者にあたります。設定した30日はそのまま有効です。

httpOnly にはもう一つ利点があって、JavaScriptから読み書きできないため、スクリプトを差し込まれる形での乗っ取りに強くなります。

ひとつ注意点があります。「サーバー発行なら安全」と単純化すると危ういところがあって、外部の計測サービスをCNAMEで自社ドメインに見せかけて発行しているCookieは、Safari 16.4以降、サーバー発行でも7日で破棄される仕様になっています。今回のように、自分のドメインで動いているアプリが自分でCookieを発行している場合は該当しません。

4. プライベートブラウズは、httpOnlyでも消える

ITPをクリアしても、もう一つ問題が残ります。

Safari プライベートブラウズ
  ブラウザを閉じる
    → Cookie が全部消える(httpOnly でも消える)
    → 次回また6桁コードが必要

ログインし直しになるのは、まだ許容できます。困るのはこちらです。

QRをスキャン → ブラウザを閉じる → Cookieが消える → 「どのQRから来たお客様か」が分からなくなる

チラシを何枚配って何人が来たのかを測るために作った仕組みなので、ここが抜けると目的そのものが崩れます。

5. だから、重要なデータはCookieに預けない

解決策はシンプルで、計測に必要なデータをCookieに依存させず、DBに直接書くことにしました。3か所に分けています。

QRコードをスキャン
      ↓
① QrScanLog ──────── DBへ即時保存
   「このQRが何回読まれたか」
   Cookie も端末情報も不要
   ★ プライベートモードでも必ず残る

      ↓ メール認証・登録が完了

② acquisitionSource ── お客様レコードに保存(初回のみ)
   「このお客様はどのQRから来たか」
   ★ 一度DBに入れば消えない。機種変更しても残る

      ↓ 注文するとき

③ orderSource ─────── Cookie → 注文レコードに保存
   「この注文はどのQRが起点か」
   ★ Cookieがあれば記録。なければ null

①はスキャンされた瞬間にサーバーへ飛ばすので、ブラウザ側に何も残らなくても記録されます。②は認証・登録という「必ずサーバーを通る瞬間」に確定させるので、そこから先は端末の状態と無関係です。

Cookieに頼っているのは③だけです。ここが欠けると「3回目の注文がチラシB経由だった」といった注文単位の紐づけは落ちますが、「Aさんはチラシ経由で来たお客様である」という一番知りたい情報は残ります。

6. 状況別に見るとこうなる

状況ログイン維持QRスキャン数顧客の流入元注文の流入元
通常ブラウズ(30日以内)自動記録記録記録
通常ブラウズ(30日経過)再認証記録記録記録
Safari通常(7日経過)影響なし記録記録記録
Safariプライベート毎回認証記録記録欠けることあり
スマホ買い替え再認証記録記録記録
ブラウザ変更再認証記録記録記録

再認証が必要になる場面はそれなりにあります。ただ、そこはメールで6桁コードを送るだけなので、パスワード再発行のような詰まり方はしません。一方で、集計に使う数字はどの状況でも欠けない。役割の分け方としては、これでいいと考えています。

7. 全体像

              ┌──────────────────────────┐
              │  サーバー(DB)           │
              │  ・QrScanLog              │ ← スキャン時に確定
              │  ・acquisitionSource      │ ← 登録時に確定
              │  ・orderSource            │ ← 注文時に記録
              └──────────────────────────┘
                          ↕
┌────────────────────────────────────────────┐
│  お客様のスマホ(ブラウザ)                 │
│  ・porder_sess(30日)  ログイン状態        │
│  ・porder_qr_src(30日)QR流入元の補助      │
│                                             │
│  ※ プライベートモードは閉じると全消え      │
│  ※ 7日制限はこのCookieには適用されない     │
└────────────────────────────────────────────┘

壊れる前提で設計する

今回いちばん考えたのは、「Cookieが消えたときに何が失われるか」でした。

ブラウザ側のストレージは、こちらでは制御できません。プライバシー保護の仕様は今後さらに厳しくなる方向で、数年前に通用した前提が来年も通用する保証はない。だとすれば、消えても構わないものだけをブラウザに置くのが素直です。

  • Cookie(端末側) … セッション維持と、注文元の補助的な記録
  • DB(サーバー側) … スキャン数と、顧客の流入元

「Safariのプライベートモードで壊れませんか」という問いに対する答えは、「ログインは壊れます。集計は壊れません」です。どちらも壊れない設計にしようとすると複雑になりすぎるので、壊れていい方を決めておく。

小さな仕組みを長く運用するには、そのくらいの割り切りがちょうどいいと思っています。

シェアポストLINEシェア