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

概要

2022 年 2 月 16 日、Google は、より安全な OAuth フローを使用して Google OAuth のインタラクションをより安全にする計画を発表しました。このガイドでは、OAuth アウトオブバンド(OOB)フローからサポートされている代替フローに移行するために必要な変更と手順について説明します。

この取り組みは、Google の OAuth 2.0 認可エンドポイントとのやり取り中に発生するフィッシング攻撃やアプリのなりすまし攻撃に対する保護対策です。

OOB とは何ですか?

OAuth out-of-band(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 Cloud コンソールの OAuth 同意画面に登録されているサポート メールアドレスを表示します。

移行プロセスを完了するには、主に次の 2 つの手順を行います。

  1. 影響を受けるかどうかを判断します。
  2. 影響を受ける場合は、より安全な代替手段に移行してください。

影響を受けるかどうかを判断する

このサポート終了は、製品版アプリ(公開ステータスが [製品版] に設定されているアプリ)にのみ適用されます。公開ステータスが「テスト中」のアプリでは、このフローは引き続き機能します。

Google Cloud コンソールの OAuth ブランディング ページで公開ステータスを確認し、公開ステータスが「本番環境」のプロジェクトで 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(Android、iOS)を使用するように移行する必要があります。

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

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

Android で Google API にアクセスする

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

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

List<Scopes> 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<Scopes> 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)か、REST 用 Objective-C 向け Google API クライアント ライブラリでフェッチャー オーソライザ(GTMFetcherAuthorizationProtocol)を使用します。

クライアントサイドで 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 following example 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 API へのアクセスが簡素化され、Google エンドポイントへのすべての呼び出しが処理されます。

サーバーサイド(オフライン)アクセス

サーバーサイド(オフライン)アクセス モードでは、次の操作を行う必要があります。

  • サーバーを立ち上げ、認可コードを受信するための一般公開エンドポイント(リダイレクト URI)を定義します。
  • Google Cloud コンソールの [クライアント] ページでリダイレクト 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)フローを使用するように移行する必要があります。