概要
2022 年 2 月 16 日、Google は、より安全な OAuth フローを使用して Google OAuth のインタラクションをより安全にするという計画を 発表しました。このガイドでは、OAuth アウトオブバンド(OOB)フローからサポートされている代替フローへの移行を成功させるために必要な変更と手順について説明します。
これは、Google の OAuth 2.0 認可エンドポイントとのやり取り中に発生するフィッシング攻撃やアプリのなりすまし攻撃に対する保護対策です。
OOB とは
OAuth アウトオブバンド(OOB)(手動のコピー/貼り付け オプションとも呼ばれます)は、ユーザーが OAuth 同意リクエストを承認した後に認証情報を受け入れるリダイレクト URI を持たないネイティブ クライアントをサポートするために開発されたレガシー フローです。OOB フローはリモート フィッシングのリスクをもたらすため、この脆弱性を防ぐには、クライアントは別の方法に移行する必要があります。OOB フローは、ウェブ アプリケーション、Android、iOS、ユニバーサル Windows プラットフォーム(UWP)、Chrome アプリ、テレビ、入力制限のあるデバイス、デスクトップ アプリなど、すべてのクライアント タイプで非推奨になります。
主なコンプライアンス スケジュール
- 2022 年 2 月 28 日 - OOB フローでの新しい OAuth の使用をブロック
- 2022 年 9 月 5 日 - 準拠していない OAuth リクエストに対して、ユーザー向けの警告メッセージが表示される可能性があります。
- 2022 年 10 月 3 日 - 2022 年 2 月 28 日より前に作成された OAuth クライアントに対して、OOB フローが非推奨になりました。
- 2023 年 1 月 31 日 - すべての既存のクライアントがブロックされます(除外対象のクライアントを含む)
準拠していないリクエストには、ユーザー向けのエラー メッセージが表示されます。このメッセージは、アプリがブロックされていることをユーザーに伝え、Google API Console の OAuth 同意画面で登録したサポート メールアドレスを表示します。
- 影響を受けるかどうかを判断します。
- 影響を受けている場合は、より安全な代替手段に移行してください。
影響を受けるかどうかを確認する
この非推奨は、本番環境アプリ(公開ステータスが [ 本番環境] に設定されているアプリ)にのみ適用されます。 テスト公開ステータスのアプリに対しては、このフローは引き続き機能します。
の OAuth でパブリケーション ステータスを確認し、パブリケーション ステータスが「製品版」のプロジェクトで OOB フローを使用している場合は、次のステップに進みます。
アプリが OOB フローを使っているかどうかを確認する方法
アプリのコードまたは送信ネットワーク呼び出し(アプリが OAuth ライブラリを使用している場合)を調べて、アプリが行っている Google OAuth 認証リクエストで OOB リダイレクト URI 値が使用されているかどうかを確認します。
アプリケーション コードを検査する
redirect_uri
パラメータに次のいずれかの値が含まれているかどうかを確認します。
redirect_uri=urn:ietf:wg:oauth:2.0:oob
redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto
redirect_uri=oob
https://accounts.google.com/o/oauth2/v2/auth? response_type=code& scope=<SCOPES>& state=<STATE>& redirect_uri=urn:ietf:wg:oauth:2.0:oob& client_id=<CLIENT_ID>
送信ネットワーク呼び出しを検査する
- ウェブ アプリケーション - Chrome のネットワーク アクティビティを検査する
- Android - ネットワーク インスペクタでネットワーク トラフィックを検査する
-
Chrome アプリ
- Chrome 拡張機能のページに移動します。
- 拡張機能ページの右上にある [デベロッパー モード] チェックボックスをオンにします。
- モニタリングする拡張機能を選択します。
- 拡張機能ページの [ビューを検証] セクションで [背景ページ] リンクをクリックします。
- デベロッパー ツールのポップアップが開き、[ ネットワーク] タブでネットワーク トラフィックをモニタリングできます。
- iOS - Instruments による HTTP トラフィックの分析
- ユニバーサル Windows プラットフォーム(UWP) - Visual Studio でネットワーク トラフィックを検査する
- デスクトップ アプリ - アプリが開発されたオペレーティング システムで利用可能な ネットワーク キャプチャ ツールを使用します。
redirect_uri
パラメータに次のいずれかの値が含まれているかどうかを確認します。
redirect_uri=urn:ietf:wg:oauth:2.0:oob
redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto
redirect_uri=oob
https://accounts.google.com/o/oauth2/v2/auth? response_type=code& scope=<SCOPES>& state=<STATE>& redirect_uri=urn:ietf:wg:oauth:2.0:oob& client_id=<CLIENT_ID>
安全な代替手段に移行する
モバイル クライアント(Android / iOS)
アプリが Android または iOS の OAuth クライアント タイプで OOB フローを使用していることが判明した場合は、推奨される SDK(Android、iOS)に移行する必要があります。
この SDK を使用すると、Google API に簡単にアクセスでき、Google の OAuth 2.0 認可エンドポイントへのすべての呼び出しを処理できます。
以下のドキュメントのリンクには、推奨される SDK を使用して、OOB リダイレクト URI を使用せずに Google API にアクセスする方法が記載されています。
Android で Google API にアクセスする
クライアントサイド アクセス
次の例は、推奨される Google Identity Services Android ライブラリを使用して、Android のクライアント側で Google API にアクセスする方法を示しています。
ListrequestedScopes = Arrays.asList(DriveScopes.DRIVE_APPDATA); AuthorizationRequest authorizationRequest = AuthorizationRequest.builder().setRequestedScopes(requestedScopes).build(); Identity.getAuthorizationClient(activity) .authorize(authorizationRequest) .addOnSuccessListener( authorizationResult -> { if (authorizationResult.hasResolution()) { // Access needs to be granted by the user PendingIntent pendingIntent = authorizationResult.getPendingIntent(); try { startIntentSenderForResult(pendingIntent.getIntentSender(), REQUEST_AUTHORIZE, null, 0, 0, 0, null); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "Couldn't start Authorization UI: " + e.getLocalizedMessage()); } } else { // Access already granted, continue with user action saveToDriveAppFolder(authorizationResult); } }) .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));
定義したメソッドに authorizationResult
を渡して、ユーザーのドライブ フォルダにコンテンツを保存します。authorizationResult
には、アクセス トークンを返す
getAccessToken()
メソッドがあります。
サーバーサイド(オフライン)アクセス
次の例は、Android のサーバーサイドで Google API にアクセスする方法を示しています。ListrequestedScopes = Arrays.asList(DriveScopes.DRIVE_APPDATA); AuthorizationRequest authorizationRequest = AuthorizationRequest.builder() .requestOfflineAccess(webClientId) .setRequestedScopes(requestedScopes) .build(); Identity.getAuthorizationClient(activity) .authorize(authorizationRequest) .addOnSuccessListener( authorizationResult -> { if (authorizationResult.hasResolution()) { // Access needs to be granted by the user PendingIntent pendingIntent = authorizationResult.getPendingIntent(); try { startIntentSenderForResult(pendingIntent.getIntentSender(), REQUEST_AUTHORIZE, null, 0, 0, 0, null); } catch (IntentSender.SendIntentException e) { Log.e(TAG, "Couldn't start Authorization UI: " + e.getLocalizedMessage()); } } else { String authCode = authorizationResult.getServerAuthCode(); } }) .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));
authorizationResult
には、認証コードを返す
getServerAuthCode()
メソッドがあります。この認証コードをバックエンドに送信して、アクセス トークンと更新トークンを取得できます。
iOS アプリで Google API にアクセスする
クライアントサイド アクセス
次の例は、iOS のクライアントサイドで Google API にアクセスする方法を示しています。
user.authentication.do { authentication, error in guard error == nil else { return } guard let authentication = authentication else { return } // Get the access token to attach it to a REST or gRPC request. let accessToken = authentication.accessToken // Or, get an object that conforms to GTMFetcherAuthorizationProtocol for // use with GTMAppAuth and the Google APIs client library. let authorizer = authentication.fetcherAuthorizer() }
アクセス トークンを使用して API を呼び出します。REST リクエストまたは gRPC リクエストのヘッダーにアクセス トークンを含めるか、
Objective-C for REST の Google API クライアント ライブラリで取得元の認証情報(GTMFetcherAuthorizationProtocol
)を使用します。Authorization: Bearer ACCESS_TOKEN
クライアントサイドで Google API にアクセスする方法については、クライアントサイド アクセス ガイドをご覧ください。をご覧ください。
サーバーサイド(オフライン)アクセス
次の例は、サーバーサイドで Google API にアクセスして iOS クライアントをサポートする方法を示しています。GIDSignIn.sharedInstance.signIn(with: signInConfig, presenting: self) { user, error in guard error == nil else { return } guard let user = user else { return } // request a one-time authorization code that your server exchanges for // an access token and refresh token let authCode = user.serverAuthCode }
サーバーサイドから Google API にアクセスする方法については、サーバーサイド アクセス ガイドをご覧ください。
Chrome アプリ クライアント
アプリが Chrome アプリ クライアントで OOB フローを使用していることが判明した場合は、 Chrome Identity API の使用に移行する必要があります。
次の例は、OOB リダイレクト URI を使用せずにすべてのユーザーの連絡先を取得する方法を示しています。
window.onload = function() { document.querySelector('button').addEventListener('click', function() { // retrieve access token chrome.identity.getAuthToken({interactive: true}, function(token) { // .......... // the example below shows how to use a retrieved access token with an appropriate scope // to call the Google People API contactGroups.get endpoint fetch( 'https://people.googleapis.com/v1/contactGroups/all?maxMembers=20&key=API_KEY', init) .then((response) => response.json()) .then(function(data) { console.log(data) }); }); }); };
Chrome Identity API を使用してユーザーの認証にアクセスし、Google エンドポイントを呼び出す方法については、 Chrome Identity API ガイドをご覧ください。
ウェブ アプリケーション
アプリがウェブ アプリケーションの OOB フローを使用していることが判明した場合は、Google API クライアント ライブラリのいずれかを使用するように移行する必要があります。さまざまなプログラミング言語のクライアント ライブラリは、こちらに記載されています。
これらのライブラリを使用すると、Google API に簡単にアクセスし、Google エンドポイントへのすべての呼び出しを処理できます。
サーバーサイド(オフライン)アクセス
- サーバーを構築し、認証コードを受信する一般公開のエンドポイント(リダイレクト URI)を定義します。
- の で リダイレクト URI を構成します。
次のコード スニペットは、Google Drive API を使用して、OOB リダイレクト URI を使用せずに、サーバーサイドでユーザーの Google ドライブ ファイルを一覧表示する NodeJS の例を示しています。
async function main() { const server = http.createServer(async function (req, res) { if (req.url.startsWith('/oauth2callback')) { let q = url.parse(req.url, true).query; if (q.error) { console.log('Error:' + q.error); } else { // Get access and refresh tokens (if access_type is offline) let { tokens } = await oauth2Client.getToken(q.code); oauth2Client.setCredentials(tokens); // Example of using Google Drive API to list filenames in user's Drive. const drive = google.drive('v3'); drive.files.list({ auth: oauth2Client, pageSize: 10, fields: 'nextPageToken, files(id, name)', }, (err1, res1) => { // TODO(developer): Handle response / error. }); } } }
サーバーサイドから Google API にアクセスする方法については、 サーバーサイド ウェブアプリ ガイドをご覧ください。
クライアントサイド アクセス
以下の JavaScript コード スニペットは、Google API を使用してクライアントサイドでユーザーのカレンダー イベントにアクセスする例を示しています。
// initTokenClient() initializes a new token client with your // web app's client ID and the scope you need access to const client = google.accounts.oauth2.initTokenClient({ client_id: 'YOUR_GOOGLE_CLIENT_ID', scope: 'https://www.googleapis.com/auth/calendar.readonly', // callback function to handle the token response callback: (tokenResponse) => { if (tokenResponse && tokenResponse.access_token) { gapi.client.setApiKey('YOUR_API_KEY'); gapi.client.load('calendar', 'v3', listUpcomingEvents); } }, }); function listUpcomingEvents() { gapi.client.calendar.events.list(...); }
クライアントサイドから Google API にアクセスする方法については、 クライアントサイド ウェブアプリ ガイドをご覧ください。
デスクトップ クライアント
アプリがデスクトップ クライアントで OOB フローを使用していることが判明した場合は、
ループバック IP アドレス(localhost
または 127.0.0.1
)フローを使用するように移行する必要があります。