AndroidでGoogleアカウントを使ってCognito認証 #アドカレ2015

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

丹内です。 皆さんおなじみAmazon Cognitoのウェブアイデンティティフェデレーションに使うことのできるIdentity Providerに、Googleが含まれていることをご存知でしょうか。
FacebookやAmazon、Twitterでの認証事例に比べて、Googleアカウントを使った認証はあまり見ない印象です。
ということで、やってみます。

Cognito Identity Poolを作成する

こちらの記事を参考にして、Identity Poolを作成します。
Unauthenticated Identitiesは有効にしなくても大丈夫です。

IAMからIdentity Providerを設定する

Cognitoのドキュメントを見ると、

NOTE: If your app uses Google and will be available on multiple mobile platforms, you should configure it as a OpenID Connect Provider, adding all created client IDs as additional audience values to allow for better integration. To learn more about Google's cross-client identity model, see Cross-client Identity.

と注意書きがあります。
実はGoogleの認証にはサイレントログインという機能があり、FacebookやTwitterのようにCognitoを使うと、外部IdPでは同じIdentityであっても、Cognito上では異なるIdentityとなっていしまうようです。これは意図する挙動ではありません。
そのため、Google認証をIAMでIdentity Providerとして設定し、Cognitoはそれを指定するという形をとります。

まず、Google Developer Consoleでプロジェクトを作成し、「認証情報」の画面から新しい認証情報を作成します。
このときに作成するのはOAuth2.0クライアントIDで、ウェブアプリケーションを選択してください。 生成されたクライアントIDは、以下のようなフォーマットになっています。

<ProjectのID>-<ランダムな文字列>.apps.googleusercontent.com

また、後で必要なので、Android用のクライアントIDも作成してください。

次にIAMの画面から、新しい認証情報を登録します。 スクリーンショット 2015-12-23 23.33.33

  • Provider Typeは「OpenID Connect」
  • Provider URLは「https://accounts.google.com」
  • Audienceは先ほど生成したWebアプリ用GooleクライアントID

以上を入力したら、保存します。

ここまでくると、Cognito Identity Poolの編集画面でGoogleを設定できるようになっているはずです。

スクリーンショット 2015-12-23 23.43.32

チェックを入れたら、保存して、Cognitoは一旦完了です。

アプリに認証を実装する

それでは、アプリに認証を実装していきましょう。
Cognitoの処理についてのみのサンプルです。 適宜置き換えてください。

private void handleSignInResult(GoogleSignInResult result) {
        if (result.isSuccess()) {
            GoogleSignInAccount account = result.getSignInAccount();
            Log.d(TAG, "IdToken: " + account.getIdToken());

            googleAuthenticationStream(account)
                    .flatMap(this::cognitoAuthenticationStream)
                    .subscribeOn(Schedulers.newThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(
                            response -> Toast.makeText(getActivity(), provider.getCachedIdentityId(), Toast.LENGTH_SHORT).show(),
                            Throwable::printStackTrace,
                            () -> Log.d(TAG, "onCompleted")
                    );
        }
}


Observable<String> googleAuthenticationStream(final GoogleSignInAccount account) {
    return Observable.create(subscriber -> {
        String token = "";

        try {
            token = GoogleAuthUtil.getToken(getContext(), account.getEmail(), "audience:server:client_id::Webアプリ用Google Oauth2.0 クライアントID");
        } catch (UserRecoverableAuthException e) {
            e.printStackTrace();
        } catch (GoogleAuthException | IOException e) {
            e.printStackTrace();
        }

        subscriber.onNext(token);
    });
}

Observable<CognitoCachingCredentialsProvider> cognitoAuthenticationStream(final String googleOAuthToken) {
    return Observable.create(subscriber -> {
        CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
                getActivity().getApplicationContext(),
                "122345", // AWSアカウントID
                "ap-northeast-1:123-356", // Identity Pool ID
                "arn:aws:iam::122345:role/Cognito_testUnauth_Role",
                "arn:aws:iam::122345:role/Cognito_testAuth_Role",
                Regions.AP_NORTHEAST_1 // Region
        );

        Map<String, String> logins = new HashMap<>();
        logins.put("accounts.google.com", googleOAuthToken);
        credentialsProvider.setLogins(logins);
        subscriber.onNext(credentialsProvider);
    });
}

まとめ

Google認証はちょっとむずかしいですが、このようにして実装できます。