うな(。・ε・。)

Android, iOS, AppEngine まわりのめもめも

iCloudのユーザIDを使って、端末を移行したユーザをセキュアに紐付ける

アプリのデータを新しいiPhoneに移行したい!というのはよくあるニーズだと思います。 単純にはiCloud Documentsを使ってアプリを作っていれば良いのですが、デバッグのしやすさなどの兼ね合いから自社サーバを使っているところが大半だと思います。

データの移行のためには安全にIDを紐付ける必要があります。 広く用いられているのはemail, passwordを使ったアカウントや, 各種SNSでのSSOを使った紐付けかと思います。

しかしながら、一つに事前にユーザにアカウントを作ってもらってから移行してもらうことの運用の難しさです。 パスワードというのは簡単に忘れるものであり、そのパスワードを送るためのメールアドレスでさえ変わったり、忘れたりすることがしょっちゅうです。

二つに日本においてはほぼ確実に間違いなくアカウントを所持しているSNSは存在しません。LINEくらいでしょうか。そのLINEでさえも、IDの紐付けはわりと無頓着であり、携帯を替えるたびにLINEアカウントが変わるのはしょっちゅうです。 紐付けたSNSがなんだったのかを忘れてしまうこともしょっちゅうですね。

他には、電話番号認証は良い手段ですが、実は電話番号は廃止後使いまわせてしまうので、セキュアに紐付けることはできません。

他にも、QRコードなどを使って認証情報を移行するのも一つの手段ですが、実は確実ではありません。なぜなら、携帯を変えるときは古いスマホをその場で下取りに出してしまうことが多いからです。

iPhone -> iPhone の移行なら Apple ID が使える?

上述の問題が全て解決されるわけではありません。しかしながら、アカウントを移行できる可能性を一つでも上げるため、サポート用のロジックを用意することを考えます。

iPhoneを使っている人は新しい携帯もiPhoneであることがほとんどでしょうから、Apple IDを使えば紐付けは可能ではないでしょうか。

基本的には最もベーシックであるEmail+Passwordを使い、Apple IDを使った紐付けでID, Password忘れ防止など利便性を向上することができれば良いですね。

結論から言えば、iCloudの提供するCloudKitを使えば紐付けが可能です。

CloudKitを使ったアカウント紐付け

鍵となるのはCloudKitのCKContainer - fetchUserRecordIDWithCompletionHandler:です。このメソッドiCloudユーザに一意のユーザレコードを取得します。 ここで取得したレコードの - recordName は33文字の文字列IDとなっています。このIDは特定アプリのなかのiCloudユーザに一意です。ですから、他のアプリで同一ユーザが- recordNameを取得したとしても、それは違う値になります。

取得できるのは次のような文字列であり、次のようなコードで取得することができます。

_cd2f38d1db30d2fe80df12c89f463a9e

[[CKContainer defaultContainer] fetchUserRecordIDWithCompletionHandler:^(CKRecordID *recordID, NSError *error) {
    if (error != nil) {
        NSLog(@"%@", error);
        return;
    }
    NSLog(@"%@", recordID.recordName);
}];

このIDは36^32通りの名前空間がありますから推論は極めて困難ですが、もう一段階ロックを付ければよりセキュアです。

CloudKitにはUserのみにアクセスできるPrivate Containerがあります。 このPrivate Containerに十分長いキーsecretを保存しておけば2つの十分長いトークンで認証を行うことができ、セキュアです。

フローとしては、次のようになります。

  1. (端末A) アプリはrecordID.recordNameが取得できたら、十分に長いキーsecretを生成しPrivate Containerに格納します。
  2. (端末A) アプリは自社サーバにrecordID.recordName, secretを送信します。
  3. (端末B) 携帯を移行してアプリを起動したとき、recordID.recordNameを取得します。また、secretをPrivate Containerから取得します。
  4. (端末B) これらの値を自社サーバに送信し、(2)で送ったものと同一かどうかを確かめます。
  5. (4)が成功すれば、端末Aと端末Bは紐付けされ、同一ユーザとみなすことができます。

コードの注意点としては、secretの書き込みはいわゆるFindOrInsert処理になります。

gist.github.com