読者です 読者をやめる 読者になる 読者になる

アニマネ開発日誌

アニメアプリのアニマネの開発日誌です。

Amazon Cognitoを実装した時にハマったことまとめ

アプリのユーザー認証にAmazon Cognitoを利用したのですが、 リリースするまでに色々とハマったのでここにまとめておきます。

iOSAmazon Cognito

公式ドキュメントに載っている実装方法と、 最新バージョンの実装方法が違うのでえらく時間が掛かりました。

詳細は下記を参考にしてください。

AndroidAmazon Cognito

同期時のエラー: Synchronize failed because it exceeded the maximum retries

同期時のリトライ上限に達したというメッセージです。 理由は複数考えられますが、僕の場合はコンフリクトの解決を行なっていないことが原因でした。

ドキュメント読むと、DefaultSyncCallbackを継承して必要なイベントだけを上書きするだけで あとはデフォルトの挙動になるのかと思いきや、そうでもないようです。

結局サンプルを参考にSyncCallbackを継承して実装しました。

dataset.synchronize(new Dataset.SyncCallback() {
    @Override
    public void onSuccess(Dataset dataset, List<Record> updatedRecords) {
        Log.d(getClass().getName(),"onSuccess");
    }

    @Override
    public boolean onConflict(Dataset dataset, List<SyncConflict> conflicts) {
        Log.w(getClass().getName(),"onConflict");
        List<Record> resolvedRecords = new ArrayList<Record>();
        for (SyncConflict conflict : conflicts) {
            /* resolved by taking remote records */
            resolvedRecords.add(conflict.resolveWithRemoteRecord());
            /* alternately take the local records */
            // resolvedRecords.add(conflict.resolveWithLocalRecord());
            // /* or customer logic, say concatenate strings */
            // String newValue = conflict.getRemoteRecord().getValue()
            //     + conflict.getLocalRecord().getValue();
            // resolvedRecords.add(conflict.resolveWithValue(newValue);
        }
        dataset.resolve(resolvedRecords);
        return true;
    }

    @Override
    public boolean onDatasetDeleted(Dataset dataset, String datasetName) {
        Log.w(getClass().getName(),"onDatasetDeleted");
        return false;
    }

    @Override
    public boolean onDatasetsMerged(Dataset dataset, List<String> datasetNames) {
        Log.d(getClass().getName(),"onDatasetsMerged");
        return true;
    }

    @Override
    public void onFailure(DataStorageException dse) {
        Log.e(getClass().getName(),dse.getMessage());
    }
});

Facebookでログイン

iOS9で認証後の処理が動かない

iOS9ではopenURLの時に呼ばれるものAppDelegateのメソッドが変わっています。 iOS8以降で動かすのであれば、下記のような感じになります。(GoogleFacebookを使っている場合)

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{
    BOOL facebook = [[FBSDKApplicationDelegate sharedInstance] application:application openURL:url sourceApplication:sourceApplication annotation:annotation];
    BOOL google = [[GIDSignIn sharedInstance] handleURL:url
                                      sourceApplication:sourceApplication
                                             annotation:annotation];;
    return facebook || google;
}

- (BOOL)application:(UIApplication *)app
            openURL:(NSURL *)url
            options:(NSDictionary *)options {
    BOOL facebook = [[FBSDKApplicationDelegate sharedInstance] application:app
                                                                   openURL:url
                                                         sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
                                                                annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
    BOOL google = [[GIDSignIn sharedInstance] handleURL:url
                               sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey]
                                      annotation:options[UIApplicationOpenURLOptionsAnnotationKey]];
    return facebook || google;
}

アプリを公開しないと自分以外で使えない

Facebookアプリを作成した直後は開発モードとなっています。 外部に公開されない代わりに自分以外に使えない状態です。

これを公開にする必要があるのですが、設定する場所が分かりづらく迷いました。

2016年6月現在はFacebookアプリの画面にある「アプリレビュー」を開くと設定できます。

f:id:animane:20160601180141p:plain

Googleでログイン

Amazon Cognito公式だとGoogle+の利用が前提になっていますが、 GoogleとしてはGoogleSignInが推奨されています。

Google+のOAuthはそのうち廃止されそうなので、今回はGoogleSignInで実装しました。

流れとしては下記のとおりです。

  1. APIを利用するアカウントでGoogleにログイン
  2. 上記の画面から設定ファイルを作成
  3. 設定ファイルを各プロジェクトに設置
  4. コードの実装

上記で設定ファイルを作ると、GoogleAPIマネージャーの認証情報に追加されています。 Google API Console

iOS版とAndroid版を使いたい時

Amazon CognitoでiOS版とAndroidの版両方で使う場合は、 OpenIDのプロバイダーとして追加する必要があります。

これは、GoogleのOAuthはプラットフォーム毎に別のユーザーとして認証されるためです。

Cognito側の設定は下記が参考になりますが、2016年5月現在は同じ方法で動きませんでした。

ポイントとしては、下記を行う必要があります。

  1. IDプロバイダーにはWeb用、iOS用、Android用のクライアントIDを全て追加しておく。
  2. サムプリントにはAndroidの設定ファイルを作成した時の署名証明書フィンガープリント(SHA-1)の値を追加する。

iOS版で設定ファイルを追加するとアプリが動かなくなった

Google Analyticsが既に導入されていませんか? Google Analyticsを導入しているのであれば、設定ファイルを作成する時にトラッキングIDを追加してあげる必要があります。

他のサービスも同様なので、既に導入済みのサービスがあれば全ての情報を入力した上で設定ファイルを作っておきましょう。

Android

IDトークンが取得できない

CognitoにはIDトークンを渡す必要があります。

Googleでサインインしたあとに得られるGoogleSignInResultからgetSignInAccount()を呼び出すとGoogleSignInAccountオブジェクトを取得できます。 GoogleSignInAccountオブジェクトには「getIdToken」というメソッドがあるので、 これでIDトークンが取得できるのかと思いきや、すんなりは取得できません。

ポイントは2つ。

  1. GoogleSignInOptionsを生成する時にrequestIdTokenを呼び出す
  2. requestIdTokenに渡すクライアントIDはWeb用のクライアントIDを渡す

コードにすると下記になります。

String serverClientId = context.getString(R.string.google_oauth_client_id);
googleSignInOptions = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestEmail()
        .requestIdToken(serverClientId)
        .build();

(参考)

Enabling Server-Side Access  |  Google Sign-In for Android  |  Google Developers

android - Google Sign-In requestIdToken returns null - Stack Overflow

しばらくすると認証ができなくなる

GoogleのIDトークンは1時間で有効期限が切れます。 そのため、適度なタイミングでログインする必要があります。

iOS版、Android版ともにsilentSignIn()という認証後にダイアログを出さずに再認証を行うメソッドがあるので、これを適宜呼び出してあげます。

アニマネの場合は同期タイミングが起動時と終了時なので、その時に呼び出すようにしています。

Googleで認証する時のデバッグについて

Gooole SignInでは認証後の情報Json Web Token(以降JWT)が使われています。 JWTというのはざっくり言うとJSONをURLに含めても問題ないようにエンコードするための規格です。

トークンの有効期限を調べたい時はこのJWTから情報を得ることができます。 デコードする時には下記が便利です。

その他参考

http://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/google.html

まとめ

iOSAndroidに組み込みましたが、中々骨が折れました。 これから実装する方の参考になれば幸いです。