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

概要

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 同意画面で登録したサポート用メールアドレスを表示している間、アプリがブロックされていることをユーザーに伝えます。

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

影響の有無を判断する

この非推奨は、製品版アプリ(公開ステータスが [ 製品版] に設定されたアプリ)にのみ適用されます。そのフローは、 公開ステータスのテストを行っているアプリで引き続き機能します。

Google API Console の OAuth Consent Screen pageで公開ステータスを確認します。プロジェクトで [公開中] のステータスのプロジェクトで 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 フローを使用していると判断した場合は、Google ログイン モバイル SDK(AndroidiOS)を使用するように移行する必要があります。

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

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

Android で Google API にアクセスする

サーバーサイド(オフライン)アクセス
以下の例は、Android でサーバーサイドで Google API にアクセスする方法を示しています。
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {
  GoogleSignInAccount account = task.getResult(ApiException.class);
  
  // request a one-time authorization code that your server exchanges for an
  // access token and sometimes refresh token
  String authCode = account.getServerAuthCode();
  
  // Show signed-in UI
  updateUI(account);

  // TODO(developer): send code to server and exchange for access/refresh/ID tokens
} catch (ApiException e) {
  Log.w(TAG, "Sign-in failed", e);
  updateUI(null);
}

サーバー側から Google API へのアクセス方法については、サーバーサイドのアクセスガイドをご覧ください。

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()
}

REST または gRPC リクエストのヘッダーにアクセス トークンを含めるか、 Fetch API 用の Google API クライアント ライブラリでフェッチャー承認者(GTMFetcherAuthorizationProtocol)を使用して、アクセス トークンを使用して API を呼び出します。

クライアント側で 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 API Consoleの Credentials page で、 リダイレクト 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 にアクセスする方法については、 サーバーサイド ウェブアプリ ガイドをご覧ください。

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

次のコード スニペットは、Google API を使用して、クライアント側でユーザーのカレンダーの予定にアクセスする JavaScript の例を示しています。


// 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)フローを使用するように移行する必要があります。