帯域外(OOB)フロー移行ガイド

概要

2022 年 2 月 16 日に、より安全な 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 同意画面で登録したサポート メールアドレスを表示します。

移行プロセスを完了するには、次の 2 つの主要な手順があります。
  1. 影響を受けるかどうかを判断します。
  2. 影響を受ける場合は、より安全な代替手段に移行してください。

影響を受けるかどうかを確認する

この非推奨は、製品版アプリ(公開ステータスが「 本番環境」に設定されているアプリ)にのみ適用されます。このフローは、 テスト公開ステータスのアプリでは引き続き機能します。

OAuth Branding pageの Google Cloud Console で公開ステータスを確認し、公開ステータスが「本番環境」のプロジェクトで OOB フローを使用している場合は、次のステップに進みます。

アプリが OOB フローを使用しているかどうかを確認する方法

アプリのコードまたは送信ネットワーク呼び出し(アプリが OAuth ライブラリを使用している場合)を調べて、アプリが作成している Google OAuth 認証リクエストが OOB リダイレクト URI 値を使用しているかどうかを判断します。

アプリケーション コードを検査する

Google OAuth 認可エンドポイントを呼び出しているアプリケーション コードのセクションを確認し、redirect_uri パラメータに次のいずれかの値が含まれているかどうかを判断します。
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto
  • redirect_uri=oob
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>

送信ネットワーク通話を検査する

ネットワーク呼び出しを検査する方法は、アプリケーション クライアントのタイプによって異なります。
ネットワーク呼び出しを検査する際に、Google OAuth 認可エンドポイントに送信されたリクエストを探し、redirect_uri パラメータに次のいずれかの値が含まれているかどうかを判断します。
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob
  • redirect_uri=urn:ietf:wg:oauth:2.0:oob:auto
  • redirect_uri=oob
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(AndroidiOS)を使用するように移行する必要があります。

この SDK を使用すると、Google API に簡単にアクセスでき、Google の OAuth 2.0 認証エンドポイントへのすべての呼び出しを処理できます。

以下のドキュメントへのリンクでは、推奨される SDK を使用して、OOB リダイレクト URI を使用せずに Google API にアクセスする方法について説明しています。

Android で Google API にアクセスする

クライアントサイド アクセス

次の例は、推奨の Google Identity Services Android ライブラリを使用して、Android のクライアント側で Google API にアクセスする方法を示しています。

  List requestedScopes = 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 にアクセスする方法を示しています。
  List requestedScopes = 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 リクエストのヘッダーにアクセス トークンを含める(Authorization: Bearer ACCESS_TOKEN)か、 Objective-C for REST の Google API クライアント ライブラリでフェッチャー認証子(GTMFetcherAuthorizationProtocol)を使用します。

クライアント側で Google API にアクセスする方法については、クライアントサイド アクセスガイドをご覧ください。クライアント サイドで Google API にアクセスする方法について説明します。

サーバーサイド(オフライン)アクセス
次の例は、iOS クライアントをサポートするためにサーバーサイドで Google API にアクセスする方法を示しています。
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)を定義します。
  • Google Cloud Consoleの Clients page リダイレクト URI を構成します。

次のコード スニペットは、OOB リダイレクト URI を使用せずに、Google Drive API を使用してユーザーの 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)フローを使用するように移行する必要があります。