Safariで入力したアカウント情報をiOSアプリで使う

 こんにちは。ユーザーファースト室の中村(@_nkmrh)です。 先日リリースしたクックパッドアプリ v7.6.0 には iCloud の Keychain に保存されているクックパッドアカウントを、アプリから利用する機能を追加しています。具体的には次のような機能です。

1. Mac の Safari から cookpad にログインします

Login to Safari

2. アカウント情報を iCloud Keychain に保存します

Save account and password

3. iPhoneのcookpadアプリを立ち上げ、ログインボタンをタップすると、Safari でログインしたアカウントが選択できるようになっています

select_account

 このように、Mac 又は iPhone の Safari からクックパッドを利用していた人が、アプリにログインする際、面倒な入力をせずにログイン出来るようになりました。ぜひ試してみて下さい。

 ※この機能を使用するには、事前に下記の設定が必要です。また、iOS 8がインストールされたiPhone 5以降、iPad 第4世代、iPad Air、iPad mini、iPad mini Retinaディスプレイモデル、iPod touch 第5世代でご利用いただけます。

  • Mac > System Preferences > iCloud > Keychain > ON
  • iPhone > 設定 > iCloud > キーチェーン > ON
  • iPhone > 設定 > Safari > パスワードと自動入力 > ユーザー名とパスワード > ON

実装

 以降、この機能の実装方法を紹介します。

  1. Xcodeプロジェクトの Associated Domains に webcredentials の設定を追加します
  2. apple-app-site-association ファイルをWebサイトのルートに配置します
  3. アプリから SecRequestSharedWebCredential 関数を呼び、アカウント・パスワードを取得します

1. Associated Domains

 Associated Domains にWebサイトのドメインを追加します。 - Xcode > Targets > Capabilities > Associated Domains

`webcredentials:example.com`

2. apple-app-site-association

 apple-app-site-association ファイルを作成します。このファイルは、Webサイトのルートに配置しておくもので、連携するアプリのApp Idを記述したファイルをAppleが認可する証明書で署名したものです。

  • webcredentials.jsonファイルを作成し、以下の内容を記述します。

    {"webcredentials":{"apps":["XXXXXXXXXX.com.example.myapp"]}} 

    ※(XXXXXXXXXXの部分はApp Id Prefixを指定します)

  • .p12ファイルを Keychain Access.app から書き出します。SSL証明書を右クリックで選択し書き出しを選択します。

    Certificates > SSL証明書 > Certificates.p12

    Handoffを実装する際は iPhone Developer の証明書が使えたのですが、今回私が試した範囲では iPhone Developer の証明書ではうまくいきませんでした。うまくいかない場合は、Webサイトで使用しているSSL証明書を使用して下さい。

  • webcredentials.json ファイルを Certificates.p12 ファイルで署名します。下記のシェルスクリプトを実行して下さい。

#!/bin/sh
openssl pkcs12 -in Certificates.p12 -clcerts -nokeys -out output_crt.pem
openssl pkcs12 -in Certificates.p12 -nocerts -nodes -out output_key.pem
openssl pkcs12 -in Certificates.p12 -cacerts -nokeys -out sample.ca-bundle
cat webcredentials.json | openssl smime -sign -inkey output_key.pem -signer output_crt.pem -certfile sample.ca-bundle -noattr -nodetach -outform DER > apple-app-site-association
  • 作成した apple-app-site-association をWebサイトのルートに配置します。

3. アプリの実装

 iCloud Keychain のアカウント情報を取得するには SecRequestSharedWebCredential 関数を使用します。

void SecRequestSharedWebCredential ( CFStringRef fqdn, CFStringRef account, void (^completionHandler)( CFArrayRef credentials, CFErrorRef error) );
- 第一引数の `fqdn` は取得したいWebサイトのドメイン名を指定します。`NULL` を指定すると、Associated Domains の設定に追加したドメインが使われます。

- 第二引数の `account` はアカウント名を指定します。 `NULL` を指定すると利用可能なすべてのアカウントが返ります。

- 第3引数の `completionHandler` の第一引数 `credentials` に見つかったアカウント・パスワードが格納されます。
SecRequestSharedWebCredential(NULL, NULL, ^(CFArrayRef credentials, CFErrorRef error) {
        if (!error && CFArrayGetCount(credentials) > 0) {
            CFDictionaryRef credential = CFArrayGetValueAtIndex(credentials, 0);

            // credential から アカウントとパスワードが取得できます
            NSString *email = (__bridge NSString *)(CFDictionaryGetValue(credential, kSecAttrAccount));
            NSString *pass = (__bridge NSString *)(CFDictionaryGetValue(credential, kSecSharedPassword));
            dispatch_async(dispatch_get_main_queue(), ^{
                // 取得した メールアドレスとパスワードでログインする
            });
        } else {
            NSError *_error = (__bridge NSError*)error;
            // _error ? @"アカウントは見つかりませんでした。" : @"キャンセルボタンがタップされました。";
        }
    });

 また、アカウント情報の取得の他、追加・削除(SecAddSharedWebCredential関数)・作成( SecCreateSharedWebCredentialPassword関数)が用意されています。

おわりに

いかがでしょうか。以上の手順で iOS アプリの面倒なアカウント入力を無くすことができます。ぜひ試してみてください。また、Keychain周りのライブラリ UICKeyChainStore が Shared Web Credentials に対応しているのでこちらを利用するのも良いと思います。


参考URL

iOS Developer Library > Shared Web Credentials Reference

MacからiPhoneに遷移させよう

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/