Amazon Cognitoを実装した時にハマったことまとめ
アプリのユーザー認証にAmazon Cognitoを利用したのですが、 リリースするまでに色々とハマったのでここにまとめておきます。
iOS版 Amazon Cognito
公式ドキュメントに載っている実装方法と、 最新バージョンの実装方法が違うのでえらく時間が掛かりました。
詳細は下記を参考にしてください。
Android版 Amazon 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以降で動かすのであれば、下記のような感じになります。(GoogleとFacebookを使っている場合)
- (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アプリの画面にある「アプリレビュー」を開くと設定できます。
Googleでログイン
Amazon Cognito公式だとGoogle+の利用が前提になっていますが、 GoogleとしてはGoogleSignInが推奨されています。
Google+のOAuthはそのうち廃止されそうなので、今回はGoogleSignInで実装しました。
流れとしては下記のとおりです。
上記で設定ファイルを作ると、GoogleのAPIマネージャーの認証情報に追加されています。 Google API Console
iOS版とAndroid版を使いたい時
Amazon CognitoでiOS版とAndroidの版両方で使う場合は、 OpenIDのプロバイダーとして追加する必要があります。
これは、GoogleのOAuthはプラットフォーム毎に別のユーザーとして認証されるためです。
Cognito側の設定は下記が参考になりますが、2016年5月現在は同じ方法で動きませんでした。
ポイントとしては、下記を行う必要があります。
- IDプロバイダーにはWeb用、iOS用、Android用のクライアントIDを全て追加しておく。
- サムプリントにはAndroidの設定ファイルを作成した時の署名証明書フィンガープリント(SHA-1)の値を追加する。
iOS版で設定ファイルを追加するとアプリが動かなくなった
Google Analyticsが既に導入されていませんか? Google Analyticsを導入しているのであれば、設定ファイルを作成する時にトラッキングIDを追加してあげる必要があります。
他のサービスも同様なので、既に導入済みのサービスがあれば全ての情報を入力した上で設定ファイルを作っておきましょう。
Android版
IDトークンが取得できない
CognitoにはIDトークンを渡す必要があります。
Googleでサインインしたあとに得られるGoogleSignInResultからgetSignInAccount()を呼び出すとGoogleSignInAccountオブジェクトを取得できます。 GoogleSignInAccountオブジェクトには「getIdToken」というメソッドがあるので、 これでIDトークンが取得できるのかと思いきや、すんなりは取得できません。
ポイントは2つ。
- GoogleSignInOptionsを生成する時にrequestIdTokenを呼び出す
- 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