開発記録

Googleログインでゲストデータを引き継ぐ設計|初回登録と既存会員を分けた理由

Appli Parkでは、初回のGoogle登録だけ現在のゲストIDを会員へ昇格させ、登録済み会員への再ログインではゲストデータを自動統合しません。その理由と実装の分岐、確認したケースを紹介します。

CONTENTSこの記事の目次9項目

Appli Parkでは、会員登録をしなくてもshare&do、coffee diary、習慣マネージャーなどのアプリケーションを使い始められます。 ところが、別の端末でも続きを使いたい場面では、端末のCookieだけに頼れません。 そこでGoogleログインを追加するとき、「初回登録」と「登録済み会員の再ログイン」を同じ処理にせず、ゲストデータの扱いを分けました。

登録なしですぐ使えて、必要になったらログインできるようにしたかった

最初から登録を必須にすると、少し試したいだけの人にもGoogleログインを求めることになります。Appli Parkでは、初回アクセス時にサイト共通の内部ユーザーIDを発行し、ブラウザにはそのIDへたどり着くためのゲストCookieだけを持たせています。各アプリの記録はこの内部ユーザーIDに結び付きます。ログイン後も同じIDを使えれば、アプリごとのデータをコピーしたり、所有者IDを書き換えたりせずに引き継げます。

現在のログイン画面。別端末で記録を続けられることと、初回ログイン時の引き継ぎを案内しています。

ゲスト利用・初回登録・再ログインの3つの場面

3つの利用場面と、データの紐付け
利用場面データと内部ユーザーIDの関係ログイン時の扱い
未ログインのゲストサーバー上の内部ユーザーIDと、ブラウザのゲストCookie同じブラウザから初回登録した場合だけ、その内部IDを会員へ昇格
初めてGoogle登録する人ゲストの内部ユーザーID、または新しく作る会員ID有効なゲスト資格情報があれば同じIDを利用。なければ新しい会員を作成
登録済み会員のログインGoogle認証に紐付いた既存の内部ユーザーID現在のブラウザに別のゲストデータがあっても自動統合しない

初回登録時だけ、ゲストの内部ユーザーIDを引き継ぐ

Google認証から戻ると、バックエンドはまず、認証結果の発行元と利用者を表すsubの組み合わせが、すでにAppli Parkの会員へ登録されているかを調べます。 まだ登録されていないGoogleアカウントで、現在のブラウザに有効なゲストCookieがあれば、そのゲストの内部ユーザーを新しい会員へ昇格させます。 各アプリケーションのデータは同じ内部ユーザーIDを参照しているため、個別にコピーする必要がありません。 昇格後は元のゲスト資格情報を失効させ、新しい会員セッションを発行します。

CODEMemberSessionService.kt(要点を抜粋)kotlin
val existingIdentity = findByIssuerAndSubject(issuer, subject)
val guestIdentity =
    if (existingIdentity == null) resolveGuestCookie() else null

val user = existingIdentity?.user
    ?: guestIdentity?.user
    ?: createMember()

if (existingIdentity == null) {
    linkExternalIdentity(user, issuer, subject)
}
if (guestIdentity != null) revokeGuestCookie()
実際の処理を読みやすく短くしたものです。先に既存会員を探し、見つからない場合だけゲスト資格情報を解決します。

既存会員へゲストデータを自動統合しない理由

  • 意図しない上書き:別端末で作った新しいゲスト記録が、会員側の既存記録へ勝手に混ざる可能性がある
  • 別人のデータ混入:共有端末では、直前に使ったゲストとGoogleアカウントの本人が同じとは限らない
  • 重複データ:同じ内容をゲスト側と会員側の両方で作っていた場合、自動判定だけでは正しくまとめられない

このため、Googleアカウントがすでに登録済みなら、現在のブラウザにゲストCookieがあってもゲスト資格情報を解決しません。 既存会員の内部ユーザーIDで新しいセッションを作り、ゲスト側はそのまま残します。 便利さだけを優先して自動統合すると、誰のデータかという境界を壊しかねないためです。 後付けで統合するなら、対象データを利用者に見せ、アプリごとの重複ルールと確認操作を用意する必要があります。

氏名・メールアドレス・プロフィール画像を保存しない判断

ログインの目的は、GoogleのプロフィールをAppli Park内に表示することではなく、同じ本人が別端末から同じ内部ユーザーIDへ戻れるようにすることです。 バックエンドが保存する外部認証情報は、Cognitoの発行元とsubの組み合わせです。 氏名、メールアドレス、プロフィール画像、Googleのパスワードは、Appli ParkのDB・画面・アプリログへ保存しません。 ブラウザにもGoogleやCognitoのトークンを置かず、バックエンドが認証コードを交換したあと、Appli Park独自のHttpOnlyセッションCookieを発行します。

ログイン時に動きを分けた6つのケース

ログイン時に動きを分けた6つのケース
ケース期待する動き実装上の扱い
ゲストデータあり+初回登録ゲストの記録をそのまま使えるゲストの内部ユーザーをMEMBERへ変更し、外部認証IDを追加。ゲスト資格情報は失効
ゲストデータなし+初回登録空の新規会員として始まる新しい内部ユーザーを作成し、Cognitoのissuer・subを対応付ける
登録済み会員+別のゲストCookieあり既存会員のデータへ戻る既存の外部認証IDを優先し、現在のゲストは解決も失効もさせない
登録済み会員+別端末同じ会員データを利用できるGoogle認証から既存の内部ユーザーを特定し、その端末用の会員セッションを発行
Cookie削除後に初回登録削除前の未登録ゲストは復元しない有効なゲスト資格情報がないため、新しい会員を作成
引き継ぎ処理の重複実行同じゲストを二重移行しない1回目で外部認証IDが登録されるため、次回以降は既存会員の分岐へ進む

今回実装しなかったこと

  • 登録済み会員へのゲストデータの後付け統合
  • Googleの氏名・メールアドレス・プロフィール画像の取得・表示
  • ゲストと会員の記録を画面上で選んで統合する機能

引き継ぎ対象の3アプリと、端末内に残すワインQ

share&do、coffee diary、習慣マネージャーは、ゲストでも会員でも同じサイト共通の内部ユーザーIDへデータを保存します。そのため初回登録時にIDを維持すれば、3アプリを個別に移行する処理は要りません。一方、ワインQの「あとで見返す」問題IDはlocalStorageへ保存しており、Googleログインとは結び付けていません。すべてのデータを一律に同期するのではなく、用途と保存場所を分けています。 ※今後作成するアプリケーションのデータについては、適宜判断していきます!

READ MOREshare&doを使う共有リストの作成・参加・TODO更新を、ゲストのまま始められます。READ MOREcoffee diaryを使う豆や淹れ方、苦味・酸味の記録を同じ内部ユーザーIDへ保存します。READ MORE習慣マネージャーを使う登録した習慣と日ごとの達成状況を、ログイン後も続けられます。READ MOREワインQを使う「あとで見返す」問題は端末内保存のため、今回は会員同期の対象外です。

引き継ぎは、コピーより「同じ利用者として続ける」

今回の設計で大切にしたのは、初回登録ではゲスト時代の内部ユーザーをそのまま会員へ昇格し、登録済み会員では別のゲストを勝手に混ぜないことです。 同じGoogleログインでも、初回と再ログインでは守るべきデータが違います。便利そうだから自動統合するのではなく、どのIDを正とするか、混ぜてよいデータか、失敗したときに戻せるかを先に決める必要がありました。

出典・参考資料OpenID ConnectGoogle for Developers。認証リクエスト、state・nonce、IDトークン検証などの基本フローを確認。developers.google.com