[この記事は Isabella Chen、ソフトウェア エンジニアによる Android Developers Blog の記事 "Improving the Security and User Experience of your Google Sign In Implementation" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。]
Google Play サービス 8.3 で、大幅に改良された Sign-In APIが公開されました。ユーザー エクスペリエンスを改善し、サーバーの認証と認可がより簡単になりました。多くのデベロッパーから、これらの API はシンプルで、エラーも発生しにくいとの声が寄せられています。しかし、Play Store のアプリケーションを見ると、現在でも多くのアプリで従来の Plus.API や GoogleAuthUtil.getToken が使われており、認証と認可のベストプラクティスに従っていないことがわかりました。ベストプラクティスに従わないと、アプリは攻撃に対して簡単に脆弱になってしまう可能性があります。
さまざまな API フローのセキュリティ実装の管理や、最新の API を使用して最新の環境を維持することに前向きなデベロッパーは、Firebase 認証を使うと認証ライフサイクル全体を管理することができます。是非ご検討ください。
解決策
共通の問題として次のようなことが挙げられます:
図 3. GET_ACCOUNTS 実行時パーミッションおよび冗長なユーザー エクスペリエンス
最大の問題は GET_ACCOUNTS パーミッションです。Marshmallow 以降では、このパーミッションは「連絡先」としてユーザーに表示されます。多くのユーザーは、この実行時パーミッションにアクセス権を与えたくありません。
解決策
新しいAuth.GOOGLE_SIGN_IN_APIに切り替えて、直感的な 1 タップ インターフェースにすることで、合理的にユーザーとの同意形成を実現し、アプリにユーザーの名前、メールアドレス、およびプロフィール画像を取得できるようになります。ユーザーがアカウントを選択したときに、アプリは OAuth 認可を受け取り、ユーザーは他のデバイスに簡単にサインインできるようになります。詳細はこちらから
図 4. 新しい合理化された 1 タップ サインイン エクスペリエンス
誤ったパターン、コピー禁止
GoogleAuthUtil.getToken はメールアドレスを必要とし、これは図 3 に示す望まれないユーザー エクスペリエンスの原因となります。また、名前、プロフィール画像 URL などのユーザーのプロファイル情報は、サーバーに保存する貴重な情報です。Auth.GOOGLE_SIGN_IN_APIを介して取得された ID トークンにはプロファイル情報が含まれ、サーバーではこれらの取得するための追加のネットワーク呼び出しが不要となります。
解決策 新しい Auth.GOOGLE_SIGN_IN_APIを使用している ID トークン フローに切り替えて、1 タップ エクスペリエンスを実現します。詳細については、このブログ記事と移行ガイドも参考になります。
誤ったパターン、コピー禁止
この実装には図 3 に示す冗長なユーザー エクスペリエンスの可能性があるのに加え、ユーザーが過去アプリにサインインしたことがある場合に新しいデバイスに切り替えると、次のような紛らわしいダイアログが表示されるという問題があります。
![]()
図 5. 認証コードに GoogleAuthUtil.getToken を使用した場合に再訪ユーザーに表示される紛らわしい同意ダイアログ
解決策
この「オフライン アクセス権を持つ」同意ダイアログを簡単に回避するには、新しい Auth.GOOGLE_SIGN_IN_APIを使用しているサーバー認証コードフローに切り替える必要があります。以前サインインしたユーザーに対しては、ダイアログを表示せずに認証コードが発行されます。詳細については、このブログ記事と移行ガイドも参考になります。
また、Google Play サービス SDK 9.0 以降では、GoogleAuthUtil.getToken および次の関連クラスを使用するために -auth の SDK の分岐を含める必要があります。
AccountChangeEvent/AccountChangeEventsRequest/AccountChangeEventsResponse
Posted by Eiji Kitamura - Developer Relations Team
Google Play サービス 8.3 で、大幅に改良された Sign-In APIが公開されました。ユーザー エクスペリエンスを改善し、サーバーの認証と認可がより簡単になりました。多くのデベロッパーから、これらの API はシンプルで、エラーも発生しにくいとの声が寄せられています。しかし、Play Store のアプリケーションを見ると、現在でも多くのアプリで従来の Plus.API や GoogleAuthUtil.getToken が使われており、認証と認可のベストプラクティスに従っていないことがわかりました。ベストプラクティスに従わないと、アプリは攻撃に対して簡単に脆弱になってしまう可能性があります。
さまざまな API フローのセキュリティ実装の管理や、最新の API を使用して最新の環境を維持することに前向きなデベロッパーは、Firebase 認証を使うと認証ライフサイクル全体を管理することができます。是非ご検討ください。
アプリを確実に保護する
デベロッパーは、自分のアプリに以下の脆弱性がないかを確認する必要があります。- メールまたはユーザー ID 置換攻撃 Android で Google を使ってサインインした後に、電子メールやユーザー ID をクレデンシャルとしてプレーン テキストのままバックエンド サーバーに送信するアプリがあります。こういったサーバー エンドポイントでは、悪意のある攻撃者が電子メールやユーザー ID を推測してリクエストを偽造し、ユーザーのアカウントに簡単にアクセスできてしまいます。 図 1. メール / ユーザー ID 置換攻撃
多くのデベロッパーが、Plus.API のgetAccountName
やgetId
使ってこのアンチパターンを実装しています。
問題のある実装例、コピー禁止
- アクセス トークン置換攻撃 Android で Google を使ってサインインした後に、多くのアプリは GoogleAuthUtil.getToken を介して取得したアクセス トークンを、アプリのバックエンド サーバーにアイデンティティアサーションとして送信します。アクセス トークンは Bearer トークンであり、バックエンド サーバーはトークンが自身に対して発行されたかどうかを簡単には確認できません。悪意のある攻撃者は、ユーザーを誘導して別のアプリケーションにサインインさせて、別のアクセス トークンを使ってバックエンドへのリクエストを偽造することができてしまいます。
図 2. アクセス トークン置換攻撃
次の例のように、多くのデベロッパーは、GoogleAuthUtil を使ってこのアンチパターンを実装し、アクセス トークンを取得してこれをサーバーに送信して認証を行っています。
問題のある実装例、コピー禁止
解決策
- アプリに上記のアンチパターンを構築した多くのデベロッパーは、ユーザーのプロファイルを得るために、デバッグのみの tokenInfo(www.googleapis.com/oauth2/v3/tokeninfo)を単純に呼び出しているか、不必要に G+(www.googleapis.com/plus/v1/people/me)エンドポイントを呼び出しています。これらのアプリは、代わりに Google が推奨する ID トークン フロー(このブログ記事で説明)を実装する必要があります。移行ガイドを確認して、安全で効率的なパターンに移行してください。
- サーバーでドライブなどのその他の Google サービスへのアクセスが必要な場合、サーバー認証コードフローを使用する必要があります。このブログ記事も参考になります。参考までに、サーバー認証コードフローを使用して ID トークンも取得できます。これから、追加のネットワーク呼び出しを行わずに、ユーザー ID / メール / 名前 / プロファイル URL を取得できます。移行ガイドを参照してください。
アプリのユーザー エクスペリエンスとパフォーマンスの改善
未だにサーバー認証に GoogleAuthUtil を使っているアプリが多く存在します。そのようなアプリのユーザーは、改善されたユーザー エクスペリエンスを使う機会を失い、一方デベロッパーは、複雑な実装をメンテナンスし続ける必要があります。共通の問題として次のようなことが挙げられます:
不必要なパーミッションをリクエストし、冗長なユーザー エクスペリエンスを表示する
多くのアプリは GET_ACCOUNTS パーミッションをリクエストし、独自のピッカーにすべてのメールアドレスを表示します。メールアドレスの取得後、アプリは GoogleAuthUtil または Plus.API を呼び出して、基本サインインのための OAuth 同意を行います。これらのアプリでは、次のような冗長なユーザー エクスペリエンスが表示されます。図 3. GET_ACCOUNTS 実行時パーミッションおよび冗長なユーザー エクスペリエンス
最大の問題は GET_ACCOUNTS パーミッションです。Marshmallow 以降では、このパーミッションは「連絡先」としてユーザーに表示されます。多くのユーザーは、この実行時パーミッションにアクセス権を与えたくありません。
解決策
新しいAuth.GOOGLE_SIGN_IN_APIに切り替えて、直感的な 1 タップ インターフェースにすることで、合理的にユーザーとの同意形成を実現し、アプリにユーザーの名前、メールアドレス、およびプロフィール画像を取得できるようになります。ユーザーがアカウントを選択したときに、アプリは OAuth 認可を受け取り、ユーザーは他のデバイスに簡単にサインインできるようになります。詳細はこちらから
図 4. 新しい合理化された 1 タップ サインイン エクスペリエンス
バックエンド用に GoogleAuthUtil から ID トークンを取得する
改良版 Sign-In APIをリリースする前は、GoogleAuthUtil.getToken は「マジック ストリング」を介して ID トークンを取得する方法が推奨されていました。誤ったパターン、コピー禁止
GoogleAuthUtil.getToken はメールアドレスを必要とし、これは図 3 に示す望まれないユーザー エクスペリエンスの原因となります。また、名前、プロフィール画像 URL などのユーザーのプロファイル情報は、サーバーに保存する貴重な情報です。Auth.GOOGLE_SIGN_IN_APIを介して取得された ID トークンにはプロファイル情報が含まれ、サーバーではこれらの取得するための追加のネットワーク呼び出しが不要となります。
解決策 新しい Auth.GOOGLE_SIGN_IN_APIを使用している ID トークン フローに切り替えて、1 タップ エクスペリエンスを実現します。詳細については、このブログ記事と移行ガイドも参考になります。
バックエンド用に GoogleAuthUtil から認証コードを取得する
以前、GoogleAuthUtil.getToken を使用して別の「マジック ストリング」を介してサーバー認証コードを取得することを推奨していました。誤ったパターン、コピー禁止
この実装には図 3 に示す冗長なユーザー エクスペリエンスの可能性があるのに加え、ユーザーが過去アプリにサインインしたことがある場合に新しいデバイスに切り替えると、次のような紛らわしいダイアログが表示されるという問題があります。

図 5. 認証コードに GoogleAuthUtil.getToken を使用した場合に再訪ユーザーに表示される紛らわしい同意ダイアログ
解決策
この「オフライン アクセス権を持つ」同意ダイアログを簡単に回避するには、新しい Auth.GOOGLE_SIGN_IN_APIを使用しているサーバー認証コードフローに切り替える必要があります。以前サインインしたユーザーに対しては、ダイアログを表示せずに認証コードが発行されます。詳細については、このブログ記事と移行ガイドも参考になります。
常に GoogleAuthUtil.getToken を使う必要がありますか?
一般的には、GoogleAuthUtil.getToken を使うべきではありません。ただし、Android クライアントで REST API 呼び出しを行っている場合を除きます。そういった場合は代わりに Auth.GOOGLE_SIGN_IN_APIを使用してください。可能な場合は必ず Google Play サービス SDK のネイティブ Android API を使用してください。これらの API は、Android 用 Google APIで確認できます。また、Google Play サービス SDK 9.0 以降では、GoogleAuthUtil.getToken および次の関連クラスを使用するために -auth の SDK の分岐を含める必要があります。
AccountChangeEvent/AccountChangeEventsRequest/AccountChangeEventsResponse
dependencies { compile 'com.google.android.gms:play-services-auth:9.0.0' }
Posted by Eiji Kitamura - Developer Relations Team