ウェブサーバー アプリケーションに OAuth 2.0 を使用する

このドキュメントでは、ウェブサーバー アプリケーションが Google API クライアント ライブラリまたは Google OAuth 2.0 エンドポイントを使用して、Google API にアクセスするための OAuth 2.0 認可を実装する方法について説明します。

OAuth 2.0 では、ユーザー名やパスワードなどの情報を秘密にしたまま、ユーザーが特定のデータをアプリケーションと共有できます。たとえば、アプリケーションで OAuth 2.0 を使い、Google ドライブにファイルを保存する許可をユーザーから取得できます。

この OAuth 2.0 フローは、ユーザー認証専用です。機密情報を保存して状態を維持できるアプリケーション向けに設計されています。適切に承認されたウェブサーバー アプリケーションは、ユーザーがアプリケーションを操作している間、またはユーザーがアプリケーションを離れた後に API にアクセスできます。

ウェブ サーバー アプリケーションでは、API リクエストの承認に サービス アカウントもよく使用されます。特に、ユーザー固有のデータではなくプロジェクト ベースのデータにアクセスするために Cloud APIs を呼び出す場合に使用されます。ウェブ サーバー アプリケーションは、ユーザー認証と組み合わせてサービス アカウントを使用できます。

クライアント ライブラリ

このページの言語固有のサンプルでは、Google API クライアント ライブラリを使用して OAuth 2.0 認証を実装しています。サンプルコードを実行するには、ご利用の言語のクライアント ライブラリをインストールしてください。

Google API クライアント ライブラリを使用してアプリケーションの OAuth 2.0 フローを処理する場合、クライアント ライブラリは、アプリケーションが独自に処理する必要がある多くの処理を実行します。たとえば、保存されたアクセス トークンをアプリが使用または更新できるタイミングや、アプリが同意を再取得する必要があるタイミングを決定します。クライアント ライブラリは、正しいリダイレクト URL を生成し、認証コードをアクセス トークンと交換するリダイレクト ハンドラの実装を支援します。

サーバーサイド アプリケーション用の Google API クライアント ライブラリは、次の言語で利用できます。

前提条件

プロジェクトでAPI を有効にする

Google API を呼び出すアプリケーションは、 API Consoleでそれらの API を有効にする必要があります。

プロジェクトで API を有効にするには:

  1. Google API ConsoleのOpen the API Library
  2. If prompted, select a project, or create a new one.
  3. API Library には、利用可能なすべての API がプロダクト ファミリーと人気度に応じて分類されて表示されます。有効にする API がリストで見当たらない場合は、検索して見つけるか、その API が属するプロダクト ファミリーで [すべて表示] をクリックします。
  4. 有効にする API を選択し、[有効にする] ボタンをクリックします。
  5. If prompted, enable billing.
  6. If prompted, read and accept the API's Terms of Service.

承認認証情報を作成する

OAuth 2.0 を使用して Google API にアクセスするアプリケーションは、Google の OAuth 2.0 サーバーに対して自身の身元を示す認証情報を持つ必要があります。次の手順では、プロジェクトの認証情報を作成する方法について説明します。アプリケーションは、この認証情報を使用して、そのプロジェクトで有効にした API にアクセスできます。

  1. Go to the Credentials page.
  2. [Create Client] をクリックします。
  3. アプリケーションの種類として [ウェブ アプリケーション] を選択します。
  4. フォームに記入し、[作成] をクリックします。PHP、Java、Python、Ruby、.NET などの言語とフレームワークを使用するアプリケーションは、認可されたリダイレクト URI を指定する必要があります。リダイレクト URI は、OAuth 2.0 サーバーがレスポンスを送信できるエンドポイントです。これらのエンドポイントは、Google の検証ルールに準拠する必要があります。

    テストでは、http://localhost:8080 など、ローカルマシンを参照する URI を指定できます。このドキュメントの例では、すべてリダイレクト URI として http://localhost:8080 を使用しています。

    アプリケーションがページ上の他のリソースに認可コードを公開しないように、アプリの認証エンドポイントを設計することをおすすめします。

認証情報を作成したら、 API Consoleから client_secret.json ファイルをダウンロードします。ご使用のアプリケーションだけがアクセスできる場所に、ファイルを安全に保存します。

アクセス スコープを特定する

スコープを指定すると、アプリケーションからのアクセス要求は必要なリソースのみに限定されるようになり、ユーザーはアプリケーションに付与するアクセスレベルを制御できます。したがって、リクエストされるスコープの数とユーザーの同意を得られる可能性の間には逆相関がある可能性があります。

OAuth 2.0 認証の実装を開始する前に、アプリがアクセス権限を必要とするスコープを設定しておくことをおすすめします。

また、アプリケーションがコンテキスト内のユーザーデータへのアクセスをリクエストする増分認可プロセスで、認可スコープへのアクセスをリクエストすることをおすすめします。このベスト プラクティスは、アプリがリクエストしているアクセス権を必要とする理由をユーザーが理解しやすくするのに役立ちます。

OAuth 2.0 API スコープのドキュメントには、Google API へのアクセスに使用できるスコープの完全なリストが記載されています。

言語固有の要件

このドキュメントのコードサンプルを実行するには、Google アカウント、インターネットへのアクセス、ウェブブラウザが必要です。API クライアント ライブラリのいずれかを使用している場合は、以下の言語固有の要件もご覧ください。

PHP

このドキュメントの PHP コードサンプルを実行するには、次のものが必要です。

  • コマンドライン インターフェース(CLI)と JSON 拡張機能がインストールされた PHP 8.0 以降。
  • Composer 依存関係管理ツール。
  • PHP 用 Google API クライアント ライブラリ:

    composer require google/apiclient:^2.15.0

詳細については、PHP 用 Google API クライアント ライブラリをご覧ください。

Python

このドキュメントの Python コードサンプルを実行するには、次のものが必要です。

  • Python 3.7 以降
  • pip パッケージ管理ツール。
  • Python 2.0 用 Google API クライアント ライブラリのリリース:
    pip install --upgrade google-api-python-client
  • ユーザー認証用の google-authgoogle-auth-oauthlibgoogle-auth-httplib2
    pip install --upgrade google-auth google-auth-oauthlib google-auth-httplib2
  • Flask Python ウェブ アプリケーション フレームワーク。
    pip install --upgrade flask
  • requests HTTP ライブラリ。
    pip install --upgrade requests

Python と関連する移行ガイドをアップグレードできない場合は、Google API Python クライアント ライブラリのリリースノートを確認してください。

Ruby

このドキュメントの Ruby コードサンプルを実行するには、次のものが必要です。

  • Ruby 2.6 以降
  • Ruby 用 Google 認証ライブラリ:

    gem install googleauth
  • ドライブとカレンダーの Google API のクライアント ライブラリ:

    gem install google-apis-drive_v3 google-apis-calendar_v3
  • Sinatra Ruby ウェブ アプリケーション フレームワーク。

    gem install sinatra

Node.js

このドキュメントの Node.js コードサンプルを実行するには、次のものが必要です。

  • Node.js のメンテナンス LTS、アクティブ LTS、または現在のリリース。
  • Google API Node.js クライアント:

    npm install googleapis crypto express express-session

HTTP/REST

OAuth 2.0 エンドポイントを直接呼び出すためにライブラリをインストールする必要はありません。

OAuth 2.0 アクセス トークンを取得する

次の手順は、ユーザーに代わって API リクエストを実行するためのユーザーの同意を得るために、アプリケーションが Google の OAuth 2.0 サーバーとやり取りする方法を示しています。ユーザーの承認が必要な Google API リクエストを実行するには、アプリがその同意を得ている必要があります。

次のリストは、これらの手順を簡単にまとめたものです。

  1. アプリケーションは必要な権限を特定します。
  2. アプリは、リクエストされた権限のリストとともにユーザーを Google にリダイレクトします。
  3. ユーザーは、アプリに権限を付与するかどうかを決定します。
  4. アプリケーションはユーザーの決定内容を把握します。
  5. ユーザーがリクエストされた権限を付与した場合、アプリケーションはユーザーに代わって API リクエストを行うために必要なトークンを取得します。

ステップ 1: 認可パラメータを設定する

まず、承認リクエストを作成します。このリクエストでは、アプリケーションを識別するパラメータを設定し、ユーザーにアプリケーションへの付与を求める権限を定義します。

  • OAuth 2.0 の認証と認可に Google クライアント ライブラリを使用する場合は、これらのパラメータを定義するオブジェクトを作成して構成します。
  • Google OAuth 2.0 エンドポイントを直接呼び出す場合は、URL を生成し、その URL にパラメータを設定します。

次のタブでは、ウェブサーバー アプリケーションでサポートされている認可パラメータを定義しています。言語固有の例では、クライアント ライブラリまたは認証ライブラリを使用して、これらのパラメータを設定するオブジェクトを構成する方法も示しています。

PHP

次のコード スニペットは、認可リクエストのパラメータを定義する Google\Client() オブジェクトを作成します。

このオブジェクトは、client_secret.json ファイルの情報を使用してアプリケーションを識別します。(このファイルの詳細については、認証情報の作成をご覧ください)。このオブジェクトは、アプリケーションがアクセス権限をリクエストしているスコープと、Google の OAuth 2.0 サーバーからのレスポンスを処理するアプリケーションの認証エンドポイントの URL も識別します。最後に、コードは省略可能な access_type パラメータと include_granted_scopes パラメータを設定します。

たとえば、次のコードは、ユーザーの Google ドライブのメタデータとカレンダーの予定への読み取り専用のオフライン アクセスをリクエストします。

use Google\Client;

$client = new Client();

// Required, call the setAuthConfig function to load authorization credentials from
// client_secret.json file.
$client->setAuthConfig('client_secret.json');

// Required, to set the scope value, call the addScope function
$client->addScope([Google\Service\Drive::DRIVE_METADATA_READONLY, Google\Service\Calendar::CALENDAR_READONLY]);

// Required, call the setRedirectUri function to specify a valid redirect URI for the
// provided client_id
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php');

// Recommended, offline access will give you both an access and refresh token so that
// your app can refresh the access token without user interaction.
$client->setAccessType('offline');

// Recommended, call the setState function. Using a state value can increase your assurance that
// an incoming connection is the result of an authentication request.
$client->setState($sample_passthrough_value);

// Optional, if your application knows which user is trying to authenticate, it can use this
// parameter to provide a hint to the Google Authentication Server.
$client->setLoginHint('hint@example.com');

// Optional, call the setPrompt function to set "consent" will prompt the user for consent
$client->setPrompt('consent');

// Optional, call the setIncludeGrantedScopes function with true to enable incremental
// authorization
$client->setIncludeGrantedScopes(true);

Python

次のコード スニペットは、google-auth-oauthlib.flow モジュールを使用して認可リクエストを構築します。

このコードは、認可認証情報の作成後にダウンロードした client_secret.json ファイルの情報を使用してアプリケーションを識別する Flow オブジェクトを構築します。このオブジェクトは、アプリケーションがアクセス権限をリクエストしているスコープと、Google の OAuth 2.0 サーバーからのレスポンスを処理するアプリケーションの認証エンドポイントの URL も識別します。最後に、コードは省略可能な access_type パラメータと include_granted_scopes パラメータを設定します。

たとえば、次のコードは、ユーザーの Google ドライブのメタデータとカレンダーの予定への読み取り専用のオフライン アクセスをリクエストします。

import google.oauth2.credentials
import google_auth_oauthlib.flow

# Required, call the from_client_secrets_file method to retrieve the client ID from a
# client_secret.json file. The client ID (from that file) and access scopes are required. (You can
# also use the from_client_config method, which passes the client configuration as it originally
# appeared in a client secrets file but doesn't access the file itself.)
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file('client_secret.json',
    scopes=['https://www.googleapis.com/auth/drive.metadata.readonly',
            'https://www.googleapis.com/auth/calendar.readonly'])

# Required, indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required. The value must exactly
# match one of the authorized redirect URIs for the OAuth 2.0 client, which you
# configured in the API Console. If this value doesn't match an authorized URI,
# you will get a 'redirect_uri_mismatch' error.
flow.redirect_uri = 'https://www.example.com/oauth2callback'

# Generate URL for request to Google's OAuth 2.0 server.
# Use kwargs to set optional request parameters.
authorization_url, state = flow.authorization_url(
    # Recommended, enable offline access so that you can refresh an access token without
    # re-prompting the user for permission. Recommended for web server apps.
    access_type='offline',
    # Optional, enable incremental authorization. Recommended as a best practice.
    include_granted_scopes='true',
    # Optional, if your application knows which user is trying to authenticate, it can use this
    # parameter to provide a hint to the Google Authentication Server.
    login_hint='hint@example.com',
    # Optional, set prompt to 'consent' will prompt the user for consent
    prompt='consent')

Ruby

作成した client_secrets.json ファイルを使用して、アプリケーションでクライアント オブジェクトを構成します。クライアント オブジェクトを構成するときに、アプリケーションがアクセスする必要があるスコープと、OAuth 2.0 サーバーからのレスポンスを処理するアプリケーションの認証エンドポイントの URL を指定します。

たとえば、次のコードは、ユーザーの Google ドライブのメタデータとカレンダーの予定への読み取り専用のオフライン アクセスをリクエストします。

require 'googleauth'
require 'googleauth/web_user_authorizer'
require 'googleauth/stores/redis_token_store'

require 'google/apis/drive_v3'
require 'google/apis/calendar_v3'

# Required, call the from_file method to retrieve the client ID from a
# client_secret.json file.
client_id = Google::Auth::ClientId.from_file('/path/to/client_secret.json')

# Required, scope value 
# Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar.
scope = ['Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY',
         'Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY']

# Required, Authorizers require a storage instance to manage long term persistence of
# access and refresh tokens.
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)

# Required, indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required. The value must exactly
# match one of the authorized redirect URIs for the OAuth 2.0 client, which you
# configured in the API Console. If this value doesn't match an authorized URI,
# you will get a 'redirect_uri_mismatch' error.
callback_uri = '/oauth2callback'

# To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI
# from the client_secret.json file. To get these credentials for your application, visit
# https://console.cloud.google.com/apis/credentials.
authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scope,
                                                token_store, callback_uri)

アプリケーションは、クライアント オブジェクトを使用して、認証リクエスト URL の生成や HTTP リクエストへのアクセス トークンの適用などの OAuth 2.0 オペレーションを実行します。

Node.js

次のコード スニペットは、認証リクエストのパラメータを定義する google.auth.OAuth2 オブジェクトを作成します。

このオブジェクトは、client_secret.json ファイルの情報を使用してアプリケーションを識別します。アクセス トークンを取得するためにユーザーに権限をリクエストするには、ユーザーを同意ページにリダイレクトします。同意ページの URL を作成するには:

const {google} = require('googleapis');
const crypto = require('crypto');
const express = require('express');
const session = require('express-session');

/**
 * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI
 * from the client_secret.json file. To get these credentials for your application, visit
 * https://console.cloud.google.com/apis/credentials.
 */
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly',
  'https://www.googleapis.com/auth/calendar.readonly'
];

// Generate a secure random state value.
const state = crypto.randomBytes(32).toString('hex');

// Store state in the session
req.session.state = state;

// Generate a url that asks permissions for the Drive activity and Google Calendar scope
const authorizationUrl = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  /** Pass in the scopes array defined above.
    * Alternatively, if only one scope is needed, you can pass a scope URL as a string */
  scope: scopes,
  // Enable incremental authorization. Recommended as a best practice.
  include_granted_scopes: true,
  // Include the state parameter to reduce the risk of CSRF attacks.
  state: state
});

重要な注意事項 - refresh_token は初回認証でのみ返されます。詳しくは、 こちらをご覧ください。

HTTP/REST

Google の OAuth 2.0 エンドポイントは https://accounts.google.com/o/oauth2/v2/auth にあります。このエンドポイントには HTTPS 経由でのみアクセスできます。プレーン HTTP 接続は拒否されます。

Google 認可サーバーは、ウェブサーバー アプリケーションに対して次のクエリ文字列パラメータをサポートしています。

パラメータ
client_id 必須

アプリケーションのクライアント ID。この値は、 にあります。

redirect_uri 必須

ユーザーが認可フローを完了した後に API サーバーがユーザーをリダイレクトする場所を指定します。この値は、クライアントの で構成した OAuth 2.0 クライアントの承認済みリダイレクト URI のいずれかと完全に一致している必要があります。この値が、指定された client_id の承認済みリダイレクト URI と一致しない場合は、redirect_uri_mismatch エラーが発生します。

http または https のスキーム、大文字と小文字の区別、末尾のスラッシュ('/')はすべて一致する必要があります。

response_type 必須

Google OAuth 2.0 エンドポイントが認可コードを返すかどうかを決定します。

ウェブ サーバー アプリケーションのパラメータ値を code に設定します。

scope 必須

アプリケーションがユーザーの代わりにアクセスできるリソースを識別するスコープのスペース区切りリスト。これらの値は、Google がユーザーに表示する同意画面に反映されます。

スコープを指定すると、アプリケーションからのアクセス要求は必要なリソースのみに限定されるようになり、ユーザーはアプリケーションに付与するアクセスレベルを制御できます。したがって、リクエストされるスコープの数とユーザーの同意が得られる可能性の間には逆相関があります。

可能な限り、コンテキストで認可スコープへのアクセスをリクエストすることをおすすめします。段階的認証を通じて、ユーザーデータへのアクセス権限を状況に合わせてリクエストすることで、ユーザーはアプリがリクエストしているアクセス権限を必要とする理由をより簡単に理解できます。

access_type 推奨

ユーザーがブラウザにいないときに、アプリケーションがアクセス トークンを更新できるかどうかを示します。有効なパラメータ値は、デフォルト値の onlineoffline です。

ユーザーがブラウザにいないときにアプリケーションでアクセス トークンを更新する必要がある場合は、値を offline に設定します。これは、このドキュメントの後半で説明するアクセス トークンの更新方法です。この値は、アプリケーションが最初に認証コードをトークンと交換するときに、更新トークンとアクセス トークンを返すよう Google 認証サーバーに指示します。

state 推奨

認可リクエストと認可サーバーのレスポンス間で状態を維持するためにアプリケーションが使用する文字列値を指定します。ユーザーがアプリのアクセス リクエストを承認または拒否すると、サーバーは、送信した値を redirect_uri の URL クエリ コンポーネント(?)の name=value ペアとして返します。

このパラメータは、ユーザーをアプリケーション内の正しいリソースに誘導する、ノンスを送信する、クロスサイト リクエスト フォージェリを軽減するなど、さまざまな目的で使用できます。redirect_uri は推測される可能性があるため、state 値を使用すると、受信接続が認証リクエストの結果であるという確信を高めることができます。クライアントの状態をキャプチャするランダムな文字列を生成するか、Cookie や別の値のハッシュをエンコードすると、レスポンスを検証して、リクエストとレスポンスが同じブラウザで生成されたことを確認できます。これにより、クロスサイト リクエスト フォージェリなどの攻撃から保護できます。state トークンを作成して確認する方法の例については、OpenID Connect のドキュメントをご覧ください。

include_granted_scopes 省略可

アプリケーションが段階的認可を使用して、コンテキスト内の追加のスコープへのアクセスをリクエストできるようにします。このパラメータの値を true に設定し、認可リクエストが承認されると、新しいアクセス トークンは、ユーザーが以前にアプリケーションへのアクセスを許可したスコープも対象になります。例については、増分承認のセクションをご覧ください。

login_hint 省略可

アプリが認証を試みているユーザーを把握している場合は、このパラメータを使用して Google 認証サーバーにヒントを提供できます。サーバーは、ヒントを使用して、ログイン フォームのメール フィールドに事前入力するか、適切なマルチログイン セッションを選択することで、ログイン フローを簡素化します。

パラメータ値をメールアドレスまたは sub 識別子に設定します。これはユーザーの Google ID と同等です。

prompt 省略可

ユーザーに表示するプロンプトのスペース区切りの大文字と小文字を区別するリスト。このパラメータを指定しない場合、ユーザーにプロンプトが表示されるのは、プロジェクトがアクセスをリクエストした最初の 1 回のみです。詳しくは、 再同意を求めるをご覧ください。

次の値があります。

none 認証画面や同意画面は表示しないでください。他の値と同時に指定することはできません。
consent ユーザーに同意を求めます。
select_account ユーザーにアカウントの選択を求める。

ステップ 2: Google の OAuth 2.0 サーバーにリダイレクトする

ユーザーを Google の OAuth 2.0 サーバーにリダイレクトして、認証と承認のプロセスを開始します。通常、これはアプリケーションがユーザーのデータに初めてアクセスする必要がある場合に発生します。増分認証の場合、このステップは、アプリケーションがまだアクセス権を持っていない追加のリソースに初めてアクセスする必要がある場合にも発生します。

PHP

  1. Google の OAuth 2.0 サーバーからアクセスをリクエストする URL を生成します。
    $auth_url = $client->createAuthUrl();
  2. お客様に $auth_url をご案内します。
    header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));

Python

この例では、Flask ウェブ アプリケーション フレームワークを使用して、ユーザーを認可 URL にリダイレクトする方法を示します。

return flask.redirect(authorization_url)

Ruby

  1. Google の OAuth 2.0 サーバーからアクセスをリクエストする URL を生成します。
    auth_uri = authorizer.get_authorization_url(request: request)
  2. お客様に auth_uri をご案内します。

Node.js

  1. ステップ 1 で生成された URL authorizationUrlgenerateAuthUrl メソッドで使用して、Google の OAuth 2.0 サーバーからアクセスをリクエストします。
  2. お客様に authorizationUrl をご案内します。
    res.redirect(authorizationUrl);

HTTP/REST

Google の認証サーバーへのリダイレクトの例

読みやすくするため、改行とスペースを挿入した URL の例を以下に示します。

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly&
 access_type=offline&
 include_granted_scopes=true&
 response_type=code&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//oauth2.example.com/code&
 client_id=client_id

リクエスト URL を作成したら、ユーザーをその URL にリダイレクトします。

Google の OAuth 2.0 サーバーはユーザーを認証し、リクエストされたスコープにアプリがアクセスすることに関してユーザーの同意を得ます。レスポンスは、指定したリダイレクト URL を使用してアプリケーションに返されます。

ステップ 3: Google がユーザーに同意を求める

このステップで、ユーザーはリクエストされたアクセス権をアプリケーションに付与するかどうかを決定します。この段階で、Google は同意ウィンドウを表示します。このウィンドウには、アプリケーションの名前と、ユーザーの認証情報を使用してアクセス権限をリクエストしている Google API サービス、付与されるアクセス スコープの概要が表示されます。ユーザーは、アプリがリクエストした 1 つ以上のスコープへのアクセス権の付与に同意するか、リクエストを拒否できます。

この段階では、アプリケーションは Google の OAuth 2.0 サーバーからのレスポンスを待機するだけで、何もする必要はありません。レスポンスには、アクセスが許可されたかどうかが示されます。このレスポンスについては、次のステップで説明します。

エラー

Google の OAuth 2.0 認可エンドポイントへのリクエストで、想定される認証フローと認可フローではなく、ユーザー向けのエラー メッセージが表示されることがあります。一般的なエラーコードと推奨される解決策を以下に示します。

admin_policy_enforced

Google Workspace 管理者のポリシーにより、リクエストされた 1 つ以上のスコープを Google アカウントが承認できません。管理者が OAuth クライアント ID に明示的にアクセス権を付与するまで、すべてのスコープまたは機密性の高い制限付きスコープへのアクセスを制限する方法について詳しくは、Google Workspace 管理者向けヘルプ記事の Google Workspace のデータにアクセスできるサードパーティ製アプリと内部アプリを制御するをご覧ください。

disallowed_useragent

認可エンドポイントは、Google の OAuth 2.0 ポリシーで禁止されている埋め込みユーザー エージェント内に表示されます。

Android

Android デベロッパーは、android.webkit.WebView で承認リクエストを開くときに、このエラー メッセージが表示されることがあります。代わりに、デベロッパーは Google Sign-In for Android や OpenID Foundation の AppAuth for Android などの Android ライブラリを使用する必要があります。

このエラーは、Android アプリが埋め込みユーザー エージェントで一般的なウェブリンクを開き、ユーザーがサイトから Google の OAuth 2.0 認可エンドポイントに移動したときに、ウェブ デベロッパーに発生する可能性があります。デベロッパーは、Android アプリリンク ハンドラまたはデフォルトのブラウザアプリを含む、オペレーティング システムのデフォルトのリンク ハンドラで一般的なリンクを開くことを許可する必要があります。Android カスタムタブ ライブラリもサポートされているオプションです。

iOS

iOS と macOS のデベロッパーは、WKWebView で承認リクエストを開くときにこのエラーが発生することがあります。代わりに、デベロッパーは Google Sign-In for iOS や OpenID Foundation の AppAuth for iOS などの iOS ライブラリを使用する必要があります。

ウェブ デベロッパーは、iOS または macOS アプリが埋め込みユーザー エージェントで一般的なウェブリンクを開き、ユーザーがサイトから Google の OAuth 2.0 認可エンドポイントに移動したときに、このエラーに遭遇する可能性があります。デベロッパーは、ユニバーサル リンク ハンドラまたはデフォルトのブラウザアプリを含む、オペレーティング システムのデフォルトのリンク ハンドラで一般的なリンクを開けるようにする必要があります。SFSafariViewController ライブラリもサポートされているオプションです。

org_internal

リクエストの OAuth クライアント ID は、特定の Google Cloud 組織内の Google アカウントへのアクセスを制限するプロジェクトの一部です。この構成オプションの詳細については、OAuth 同意画面の設定に関するヘルプ記事のユーザータイプのセクションをご覧ください。

invalid_client

OAuth クライアント シークレットが正しくありません。このリクエストで使用されるクライアント ID とシークレットを含む、OAuth クライアント構成を確認します。

deleted_client

リクエストの作成に使用されている OAuth クライアントが削除されました。削除は手動で行うことも、未使用のクライアント の場合は自動で行うこともできます。削除したクライアントは、削除後 30 日以内であれば復元できます。詳しくは、こちら をご覧ください。

invalid_grant

アクセス トークンを更新するときや、増分認可を使用するときに、トークンの有効期限が切れているか、無効になっている可能性があります。ユーザーを再度認証し、新しいトークンを取得するためのユーザーの同意を求めます。このエラーが引き続き表示される場合は、アプリケーションが正しく構成されていること、リクエストで正しいトークンとパラメータを使用していることを確認してください。それ以外の場合は、ユーザー アカウントが削除または無効になっている可能性があります。

redirect_uri_mismatch

認可リクエストで渡された redirect_uri が、OAuth クライアント ID の承認済みリダイレクト URI と一致しません。 で承認済みのリダイレクト URI を確認します。

redirect_uri パラメータは、非推奨となりサポートが終了した OAuth 帯域外(OOB)フローを参照している可能性があります。統合を更新するには、移行ガイドを参照してください。

invalid_request

リクエストに問題がありました。これには、次のような理由が考えられます。

  • リクエストの形式が正しくありませんでした
  • リクエストに必須パラメータが含まれていませんでした
  • リクエストで、Google がサポートしていない認証方法が使用されています。OAuth 統合で推奨される統合方法が使用されていることを確認する

ステップ 4: OAuth 2.0 サーバーのレスポンスを処理する

OAuth 2.0 サーバーは、リクエストで指定された URL を使用して、アプリケーションのアクセス リクエストに応答します。

ユーザーがアクセス リクエストを承認すると、レスポンスに認証コードが格納されます。ユーザーがリクエストを承認しないと、レスポンスにエラー メッセージが格納されます。ウェブサーバーに返される認証コードまたはエラー メッセージは、次のようにクエリ文字列に表示されます。

エラー レスポンス:

https://oauth2.example.com/auth?error=access_denied

認証コード レスポンス:

https://oauth2.example.com/auth?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7

OAuth 2.0 サーバー レスポンスの例

このフローをテストするには、次のサンプル URL をクリックします。この URL は、Google ドライブのファイルのメタデータを表示するための読み取り専用アクセスと、Google カレンダーの予定を表示するための読み取り専用アクセスをリクエストします。

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly&
 access_type=offline&
 include_granted_scopes=true&
 response_type=code&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//oauth2.example.com/code&
 client_id=client_id

OAuth 2.0 フローが完了すると、http://localhost/oauth2callback にリダイレクトされます。ローカルマシンがそのアドレスでファイルを配信していない限り、404 NOT FOUND エラーが発生する可能性があります。次のステップでは、ユーザーがアプリケーションにリダイレクトされたときに URI で返される情報について詳しく説明します。

ステップ 5: 認証コードを更新トークンとアクセス トークンに交換する

ウェブサーバーは認証コードを受け取った後、アクセス トークンと交換できます。

PHP

認可コードをアクセス トークンと交換するには、fetchAccessTokenWithAuthCode メソッドを使用します。

$access_token = $client->fetchAccessTokenWithAuthCode($_GET['code']);

Python

コールバック ページで、google-auth ライブラリを使用して認可サーバーのレスポンスを検証します。次に、flow.fetch_token メソッドを使用して、そのレスポンスの認証コードをアクセス トークンと交換します。

state = flask.session['state']
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'],
    state=state)
flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)

# Store the credentials in the session.
# ACTION ITEM for developers:
#     Store user's access and refresh tokens in your data store if
#     incorporating this code into your real app.
credentials = flow.credentials
flask.session['credentials'] = {
    'token': credentials.token,
    'refresh_token': credentials.refresh_token,
    'token_uri': credentials.token_uri,
    'client_id': credentials.client_id,
    'client_secret': credentials.client_secret,
    'granted_scopes': credentials.granted_scopes}

Ruby

コールバック ページで、googleauth ライブラリを使用して認可サーバーのレスポンスを検証します。authorizer.handle_auth_callback_deferred メソッドを使用して、認証コードを保存し、認証を最初にリクエストした URL にリダイレクトします。これにより、ユーザーのセッションに結果を一時的に保存することで、コードの交換が延期されます。

  target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request)
  redirect target_url

Node.js

認可コードをアクセス トークンと交換するには、getToken メソッドを使用します。

const url = require('url');

// Receive the callback from Google's OAuth 2.0 server.
app.get('/oauth2callback', async (req, res) => {
  let q = url.parse(req.url, true).query;

  if (q.error) { // An error response e.g. error=access_denied
    console.log('Error:' + q.error);
  } else if (q.state !== req.session.state) { //check state value
    console.log('State mismatch. Possible CSRF attack');
    res.end('State mismatch. Possible CSRF attack');
  } else { // Get access and refresh tokens (if access_type is offline)

    let { tokens } = await oauth2Client.getToken(q.code);
    oauth2Client.setCredentials(tokens);
});

HTTP/REST

認証コードをアクセス トークンと交換するには、https://oauth2.googleapis.com/token エンドポイントを呼び出し、次のパラメータを設定します。

フィールド
client_id から取得したクライアント ID。
client_secret から取得したクライアント シークレット。
code 初回リクエストから返された認証コード。
grant_type OAuth 2.0 仕様で定義されているように、このフィールドの値は authorization_code に設定する必要があります。
redirect_uri 指定された client_id の でプロジェクトにリストされているリダイレクト URI のいずれか。

次のスニペットは、リクエストの例を示しています。

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https%3A//oauth2.example.com/code&
grant_type=authorization_code

Google は、このリクエストに応答して、有効期間の短いアクセス トークンと更新トークンを含む JSON オブジェクトを返します。更新トークンは、アプリケーションが Google の認可サーバーへの初回リクエストaccess_type パラメータを offline に設定した場合にのみ返されます。

レスポンスには、次のフィールドが含まれます。

フィールド
access_token Google API リクエストを承認するためにアプリケーションが送信するトークン。
expires_in アクセス トークンの残りの有効期間(秒単位)。
refresh_token 新しいアクセス トークンを取得するために使用できるトークン。更新トークンは、ユーザーがアクセスを取り消すか、更新トークンの有効期限が切れるまで有効です。このフィールドは、Google の認証サーバーへの初回リクエストで access_type パラメータを offline に設定した場合にのみ、このレスポンスに存在します。
refresh_token_expires_in 更新トークンの残りの有効期間(秒単位)。この値は、ユーザーが時間ベースのアクセスを許可した場合にのみ設定されます。
scope access_token によって付与されたアクセス権のスコープ。スペース区切りの大文字と小文字が区別される文字列のリストとして表現されます。
token_type 返されるトークンのタイプ。この時点では、このフィールドの値は常に Bearer に設定されます。

次のスニペットは、サンプル レスポンスを示しています。

{
  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "token_type": "Bearer",
  "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly",
  "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}

エラー

認証コードをアクセス トークンと交換する際に、想定されるレスポンスではなく、次のエラーが発生することがあります。一般的なエラーコードと推奨される解決策を以下に示します。

invalid_grant

指定された認証コードが無効であるか、形式が正しくありません。OAuth プロセスを再開して、ユーザーに再度同意を求めることで、新しいコードをリクエストします。

ステップ 6: ユーザーがどのスコープを付与したかを確認する

複数の権限(スコープ)をリクエストする場合、ユーザーがアプリにすべての権限へのアクセスを許可するとは限りません。アプリは、実際に付与されたスコープを確認し、一部の権限が拒否された状況に適切に対応する必要があります。通常は、拒否されたスコープに依存する機能を無効にします。

ただし、例外もあります。ドメイン全体の権限の委任が設定されている Google Workspace Enterprise アプリ、または信頼できるアプリとしてマークされているアプリは、詳細な権限の同意画面をスキップします。これらのアプリでは、ユーザーにきめ細かい権限の同意画面は表示されません。代わりに、アプリはリクエストされたすべてのスコープを受け取るか、1 つも受け取らないかのどちらかになります。

詳しくは、きめ細かい権限を処理する方法をご覧ください。

PHP

ユーザーがどのスコープを付与したかを確認するには、getGrantedScope() メソッドを使用します。

// Space-separated string of granted scopes if it exists, otherwise null.
$granted_scopes = $client->getOAuth2Service()->getGrantedScope();

// Determine which scopes user granted and build a dictionary
$granted_scopes_dict = [
  'Drive' => str_contains($granted_scopes, Google\Service\Drive::DRIVE_METADATA_READONLY),
  'Calendar' => str_contains($granted_scopes, Google\Service\Calendar::CALENDAR_READONLY)
];

Python

返された credentials オブジェクトには granted_scopes プロパティがあります。これは、ユーザーがアプリに付与したスコープのリストです。

credentials = flow.credentials
flask.session['credentials'] = {
    'token': credentials.token,
    'refresh_token': credentials.refresh_token,
    'token_uri': credentials.token_uri,
    'client_id': credentials.client_id,
    'client_secret': credentials.client_secret,
    'granted_scopes': credentials.granted_scopes}

次の関数は、ユーザーがアプリに付与したスコープを確認します。

def check_granted_scopes(credentials):
  features = {}
  if 'https://www.googleapis.com/auth/drive.metadata.readonly' in credentials['granted_scopes']:
    features['drive'] = True
  else:
    features['drive'] = False

  if 'https://www.googleapis.com/auth/calendar.readonly' in credentials['granted_scopes']:
    features['calendar'] = True
  else:
    features['calendar'] = False

  return features

Ruby

複数のスコープを一度にリクエストする場合は、credentials オブジェクトの scope プロパティで、どのスコープが付与されたかを確認します。

# User authorized the request. Now, check which scopes were granted.
if credentials.scope.include?(Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY)
  # User authorized read-only Drive activity permission.
  # Calling the APIs, etc
else
  # User didn't authorize read-only Drive activity permission.
  # Update UX and application accordingly
end

# Check if user authorized Calendar read permission.
if credentials.scope.include?(Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY)
  # User authorized Calendar read permission.
  # Calling the APIs, etc.
else
  # User didn't authorize Calendar read permission.
  # Update UX and application accordingly
end

Node.js

複数のスコープを一度にリクエストする場合は、tokens オブジェクトの scope プロパティで、どのスコープが付与されたかを確認します。

// User authorized the request. Now, check which scopes were granted.
if (tokens.scope.includes('https://www.googleapis.com/auth/drive.metadata.readonly'))
{
  // User authorized read-only Drive activity permission.
  // Calling the APIs, etc.
}
else
{
  // User didn't authorize read-only Drive activity permission.
  // Update UX and application accordingly
}

// Check if user authorized Calendar read permission.
if (tokens.scope.includes('https://www.googleapis.com/auth/calendar.readonly'))
{
  // User authorized Calendar read permission.
  // Calling the APIs, etc.
}
else
{
  // User didn't authorize Calendar read permission.
  // Update UX and application accordingly
}

HTTP/REST

ユーザーが特定のスコープへのアクセス権をアプリケーションに付与しているかどうかを確認するには、アクセス トークン レスポンスの scope フィールドを調べます。access_token によって付与されたアクセス権のスコープ。スペース区切りの大文字と小文字が区別される文字列のリストとして表されます。

たとえば、次のサンプル アクセス トークン レスポンスは、ユーザーが読み取り専用のドライブ アクティビティとカレンダーの予定の権限をアプリに付与したことを示しています。

  {
    "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
    "expires_in": 3920,
    "token_type": "Bearer",
    "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly",
    "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
  }

Google API を呼び出す

PHP

次の手順で、アクセス トークンを使用して Google API を呼び出します。

  1. 新しい Google\Client オブジェクトにアクセス トークンを適用する必要がある場合(ユーザー セッションにアクセス トークンを保存した場合など)は、setAccessToken メソッドを使用します。
    $client->setAccessToken($access_token);
  2. 呼び出す API のサービス オブジェクトをビルドします。サービス オブジェクトは、呼び出す API のコンストラクタに承認済みの Google\Client オブジェクトを指定して作成します。たとえば、Drive API を呼び出すには:
    $drive = new Google\Service\Drive($client);
  3. サービス オブジェクトによって提供されるインターフェースを使用して、API サービスにリクエストを行います。たとえば、認証済みユーザーの Google ドライブ内のファイルの一覧を取得するには、次のようにします。
    $files = $drive->files->listFiles(array());

Python

アクセス トークンを取得すると、アプリケーションはそのトークンを使用して、特定のユーザー アカウントまたはサービス アカウントに代わって API リクエストを承認できます。ユーザー固有の認可認証情報を使用して、呼び出す API のサービス オブジェクトを構築し、そのオブジェクトを使用して承認済みの API リクエストを行います。

  1. 呼び出す API のサービス オブジェクトをビルドします。サービス オブジェクトは、API の名前とバージョン、ユーザー認証情報を使用して googleapiclient.discovery ライブラリの build メソッドを呼び出すことで作成します。 たとえば、Drive API のバージョン 3 を呼び出すには、次のようにします。
    from googleapiclient.discovery import build
    
    drive = build('drive', 'v2', credentials=credentials)
  2. サービス オブジェクトが提供するインターフェースを使用して、API サービスにリクエストを行います。たとえば、認証済みユーザーの Google ドライブ内のファイルの一覧を取得するには、次のようにします。
    files = drive.files().list().execute()

Ruby

アクセス トークンを取得すると、アプリケーションはそのトークンを使用して、特定のユーザー アカウントまたはサービス アカウントの代わりに API リクエストを行うことができます。ユーザー固有の認可認証情報を使用して、呼び出す API のサービス オブジェクトを構築し、そのオブジェクトを使用して承認済みの API リクエストを行います。

  1. 呼び出す API のサービス オブジェクトをビルドします。たとえば、Drive API のバージョン 3 を呼び出すには:
    drive = Google::Apis::DriveV3::DriveService.new
  2. サービスに認証情報を設定します。
    drive.authorization = credentials
  3. サービス オブジェクトによって提供されるインターフェースを使用して、API サービスにリクエストを行います。たとえば、認証済みユーザーの Google ドライブ内のファイルの一覧を取得するには、次のようにします。
    files = drive.list_files

また、メソッドに options パラメータを指定することで、メソッドごとに認可を提供することもできます。

files = drive.list_files(options: { authorization: credentials })

Node.js

アクセス トークンを取得して OAuth2 オブジェクトに設定したら、そのオブジェクトを使用して Google API を呼び出します。アプリケーションはこのトークンを使用して、特定のユーザー アカウントまたはサービス アカウントに代わって API リクエストを承認できます。呼び出す API のサービス オブジェクトをビルドします。たとえば、次のコードは Google Drive API を使用して、ユーザーのドライブ内のファイル名の一覧を取得します。

const { google } = require('googleapis');

// 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) => {
  if (err1) return console.log('The API returned an error: ' + err1);
  const files = res1.data.files;
  if (files.length) {
    console.log('Files:');
    files.map((file) => {
      console.log(`${file.name} (${file.id})`);
    });
  } else {
    console.log('No files found.');
  }
});

HTTP/REST

アプリケーションがアクセス トークンを取得した後、API で必要なアクセス スコープが付与されている場合は、トークンを使用して特定のユーザー アカウントの代わりに Google API を呼び出すことができます。これを行うには、access_token クエリ パラメータまたは Authorization HTTP ヘッダー Bearer 値のいずれかを含めることで、API へのリクエストにアクセス トークンを含めます。クエリ文字列はサーバーログに表示される傾向があるため、可能な場合は HTTP ヘッダーを使用することをおすすめします。ほとんどの場合、クライアント ライブラリを使用して Google API への呼び出しを設定できます(たとえば、Drive Files API を呼び出す場合など)。

OAuth 2.0 Playground では、すべての Google API を試して、そのスコープを確認できます。

HTTP GET の例

Authorization: Bearer HTTP ヘッダーを使用して drive.files エンドポイント(ドライブ ファイル API)を呼び出すと、次のようになります。独自のアクセス トークンを指定する必要があります。

GET /drive/v2/files HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer access_token

以下は、access_token クエリ文字列パラメータを使用して、認証済みユーザーに対して同じ API を呼び出す例です。

GET https://www.googleapis.com/drive/v2/files?access_token=access_token

curl の例

これらのコマンドは、curl コマンドライン アプリケーションでテストできます。HTTP ヘッダー オプション(推奨)を使用する例を次に示します。

curl -H "Authorization: Bearer access_token" https://www.googleapis.com/drive/v2/files

または、クエリ文字列パラメータ オプションを使用します。

curl https://www.googleapis.com/drive/v2/files?access_token=access_token

サンプルコードの全文

次の例では、ユーザーが認証を行い、ユーザーのドライブ メタデータにアクセスする権限をアプリケーションに付与した後に、ユーザーの Google ドライブ内のファイルの JSON 形式のリストが出力されます。

PHP

この例を実行するには:

  1. API Consoleで、ローカル マシンの URL をリダイレクト URL のリストに追加します。たとえば、http://localhost:8080 を追加します。
  2. 新しいディレクトリを作成し、そのディレクトリに移動します。次に例を示します。
    mkdir ~/php-oauth2-example
    cd ~/php-oauth2-example
  3. Composer を使用して PHP 用 Google API クライアント ライブラリをインストールします。
    composer require google/apiclient:^2.15.0
  4. 次の内容の index.php ファイルと oauth2callback.php ファイルを作成します。
  5. PHP の組み込みテスト ウェブサーバーでサンプルを実行します。
    php -S localhost:8080 ~/php-oauth2-example

index.php

<?php
require_once __DIR__.'/vendor/autoload.php';

session_start();

$client = new Google\Client();
$client->setAuthConfig('client_secret.json');

// User granted permission as an access token is in the session.
if (isset($_SESSION['access_token']) && $_SESSION['access_token'])
{
  $client->setAccessToken($_SESSION['access_token']);
  
  // Check if user granted Drive permission
  if ($_SESSION['granted_scopes_dict']['Drive']) {
    echo "Drive feature is enabled.";
    echo "</br>";
    $drive = new Drive($client);
    $files = array();
    $response = $drive->files->listFiles(array());
    foreach ($response->files as $file) {
        echo "File: " . $file->name . " (" . $file->id . ")";
        echo "</br>";
    }
  } else {
    echo "Drive feature is NOT enabled.";
    echo "</br>";
  }

   // Check if user granted Calendar permission
  if ($_SESSION['granted_scopes_dict']['Calendar']) {
    echo "Calendar feature is enabled.";
    echo "</br>";
  } else {
    echo "Calendar feature is NOT enabled.";
    echo "</br>";
  }
}
else
{
  // Redirect users to outh2call.php which redirects users to Google OAuth 2.0
  $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php';
  header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
?>

oauth2callback.php

<?php
require_once __DIR__.'/vendor/autoload.php';

session_start();

$client = new Google\Client();

// Required, call the setAuthConfig function to load authorization credentials from
// client_secret.json file.
$client->setAuthConfigFile('client_secret.json');
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST']. $_SERVER['PHP_SELF']);

// Required, to set the scope value, call the addScope function.
$client->addScope([Google\Service\Drive::DRIVE_METADATA_READONLY, Google\Service\Calendar::CALENDAR_READONLY]);

// Enable incremental authorization. Recommended as a best practice.
$client->setIncludeGrantedScopes(true);

// Recommended, offline access will give you both an access and refresh token so that
// your app can refresh the access token without user interaction.
$client->setAccessType("offline");

// Generate a URL for authorization as it doesn't contain code and error
if (!isset($_GET['code']) && !isset($_GET['error']))
{
  // Generate and set state value
  $state = bin2hex(random_bytes(16));
  $client->setState($state);
  $_SESSION['state'] = $state;

  // Generate a url that asks permissions.
  $auth_url = $client->createAuthUrl();
  header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
}

// User authorized the request and authorization code is returned to exchange access and
// refresh tokens.
if (isset($_GET['code']))
{
  // Check the state value
  if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['state']) {
    die('State mismatch. Possible CSRF attack.');
  }

  // Get access and refresh tokens (if access_type is offline)
  $token = $client->fetchAccessTokenWithAuthCode($_GET['code']);

  /** Save access and refresh token to the session variables.
    * ACTION ITEM: In a production app, you likely want to save the
    *              refresh token in a secure persistent storage instead. */
  $_SESSION['access_token'] = $token;
  $_SESSION['refresh_token'] = $client->getRefreshToken();
  
  // Space-separated string of granted scopes if it exists, otherwise null.
  $granted_scopes = $client->getOAuth2Service()->getGrantedScope();

  // Determine which scopes user granted and build a dictionary
  $granted_scopes_dict = [
    'Drive' => str_contains($granted_scopes, Google\Service\Drive::DRIVE_METADATA_READONLY),
    'Calendar' => str_contains($granted_scopes, Google\Service\Calendar::CALENDAR_READONLY)
  ];
  $_SESSION['granted_scopes_dict'] = $granted_scopes_dict;
  
  $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/';
  header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}

// An error response e.g. error=access_denied
if (isset($_GET['error']))
{
  echo "Error: ". $_GET['error'];
}
?>

Python

この例では、Flask フレームワークを使用します。http://localhost:8080 でウェブ アプリケーションを実行し、OAuth 2.0 フローをテストできます。この URL にアクセスすると、次の 5 つのリンクが表示されます。

  • Drive API を呼び出す: このリンクは、ユーザーが権限を付与した場合に、サンプル API リクエストの実行を試みるページに移動します。必要に応じて、認証フローを開始します。成功すると、ページに API レスポンスが表示されます。
  • カレンダー API を呼び出すモックページ: ユーザーが権限を付与した場合に、サンプル カレンダー API リクエストの実行を試みるモックページへのリンクです。必要に応じて、認証フローを開始します。成功すると、ページに API レスポンスが表示されます。
  • 認証フローを直接テストする: このリンクは、ユーザーを認証フローに誘導しようとするページを指します。アプリが、ユーザーに代わって承認済みの API リクエストを送信する権限をリクエストします。
  • 現在の認証情報を無効にする: このリンクは、ユーザーがすでにアプリケーションに付与している権限を 取り消すページに移動します。
  • Flask セッションの認証情報をクリア: このリンクをクリックすると、Flask セッションに保存されている認証情報がクリアされます。これにより、アプリにすでに権限を付与しているユーザーが新しいセッションで API リクエストを実行しようとした場合に何が起こるかを確認できます。また、ユーザーがアプリに付与した権限を取り消し、アプリが取り消されたアクセス トークンでリクエストの承認を試みた場合に、アプリが受け取る API レスポンスを確認することもできます。
# -*- coding: utf-8 -*-

import os
import flask
import requests

import google.oauth2.credentials
import google_auth_oauthlib.flow
import googleapiclient.discovery

# This variable specifies the name of a file that contains the OAuth 2.0
# information for this application, including its client_id and client_secret.
CLIENT_SECRETS_FILE = "client_secret.json"

# The OAuth 2.0 access scope allows for access to the
# authenticated user's account and requires requests to use an SSL connection.
SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly',
          'https://www.googleapis.com/auth/calendar.readonly']
API_SERVICE_NAME = 'drive'
API_VERSION = 'v2'

app = flask.Flask(__name__)
# Note: A secret key is included in the sample so that it works.
# If you use this code in your application, replace this with a truly secret
# key. See https://flask.palletsprojects.com/quickstart/#sessions.
app.secret_key = 'REPLACE ME - this value is here as a placeholder.'

@app.route('/')
def index():
  return print_index_table()

@app.route('/drive')
def drive_api_request():
  if 'credentials' not in flask.session:
    return flask.redirect('authorize')

  features = flask.session['features']

  if features['drive']:
    # Load credentials from the session.
    credentials = google.oauth2.credentials.Credentials(
        **flask.session['credentials'])

    drive = googleapiclient.discovery.build(
        API_SERVICE_NAME, API_VERSION, credentials=credentials)

    files = drive.files().list().execute()

    # Save credentials back to session in case access token was refreshed.
    # ACTION ITEM: In a production app, you likely want to save these
    #              credentials in a persistent database instead.
    flask.session['credentials'] = credentials_to_dict(credentials)

    return flask.jsonify(**files)
  else:
    # User didn't authorize read-only Drive activity permission.
    # Update UX and application accordingly
    return '<p>Drive feature is not enabled.</p>'

@app.route('/calendar')
    def calendar_api_request():
      if 'credentials' not in flask.session:
        return flask.redirect('authorize')

      features = flask.session['features']

      if features['calendar']:
        # User authorized Calendar read permission.
        # Calling the APIs, etc.
        return ('<p>User granted the Google Calendar read permission. '+
                'This sample code does not include code to call Calendar</p>')
      else:
        # User didn't authorize Calendar read permission.
        # Update UX and application accordingly
        return '<p>Calendar feature is not enabled.</p>'

@app.route('/authorize')
def authorize():
  # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      CLIENT_SECRETS_FILE, scopes=SCOPES)

  # The URI created here must exactly match one of the authorized redirect URIs
  # for the OAuth 2.0 client, which you configured in the API Console. If this
  # value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch'
  # error.
  flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

  authorization_url, state = flow.authorization_url(
      # Enable offline access so that you can refresh an access token without
      # re-prompting the user for permission. Recommended for web server apps.
      access_type='offline',
      # Enable incremental authorization. Recommended as a best practice.
      include_granted_scopes='true')

  # Store the state so the callback can verify the auth server response.
  flask.session['state'] = state

  return flask.redirect(authorization_url)

@app.route('/oauth2callback')
def oauth2callback():
  # Specify the state when creating the flow in the callback so that it can
  # verified in the authorization server response.
  state = flask.session['state']

  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
  flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

  # Use the authorization server's response to fetch the OAuth 2.0 tokens.
  authorization_response = flask.request.url
  flow.fetch_token(authorization_response=authorization_response)

  # Store credentials in the session.
  # ACTION ITEM: In a production app, you likely want to save these
  #              credentials in a persistent database instead.
  credentials = flow.credentials
  
  credentials = credentials_to_dict(credentials)
  flask.session['credentials'] = credentials

  # Check which scopes user granted
  features = check_granted_scopes(credentials)
  flask.session['features'] = features
  return flask.redirect('/')
  

@app.route('/revoke')
def revoke():
  if 'credentials' not in flask.session:
    return ('You need to <a href="/authorize">authorize</a> before ' +
            'testing the code to revoke credentials.')

  credentials = google.oauth2.credentials.Credentials(
    **flask.session['credentials'])

  revoke = requests.post('https://oauth2.googleapis.com/revoke',
      params={'token': credentials.token},
      headers = {'content-type': 'application/x-www-form-urlencoded'})

  status_code = getattr(revoke, 'status_code')
  if status_code == 200:
    return('Credentials successfully revoked.' + print_index_table())
  else:
    return('An error occurred.' + print_index_table())

@app.route('/clear')
def clear_credentials():
  if 'credentials' in flask.session:
    del flask.session['credentials']
  return ('Credentials have been cleared.<br><br>' +
          print_index_table())

def credentials_to_dict(credentials):
  return {'token': credentials.token,
          'refresh_token': credentials.refresh_token,
          'token_uri': credentials.token_uri,
          'client_id': credentials.client_id,
          'client_secret': credentials.client_secret,
          'granted_scopes': credentials.granted_scopes}

def check_granted_scopes(credentials):
  features = {}
  if 'https://www.googleapis.com/auth/drive.metadata.readonly' in credentials['granted_scopes']:
    features['drive'] = True
  else:
    features['drive'] = False

  if 'https://www.googleapis.com/auth/calendar.readonly' in credentials['granted_scopes']:
    features['calendar'] = True
  else:
    features['calendar'] = False

  return features

def print_index_table():
  return ('<table>' +
          '<tr><td><a href="/test">Test an API request</a></td>' +
          '<td>Submit an API request and see a formatted JSON response. ' +
          '    Go through the authorization flow if there are no stored ' +
          '    credentials for the user.</td></tr>' +
          '<tr><td><a href="/authorize">Test the auth flow directly</a></td>' +
          '<td>Go directly to the authorization flow. If there are stored ' +
          '    credentials, you still might not be prompted to reauthorize ' +
          '    the application.</td></tr>' +
          '<tr><td><a href="/revoke">Revoke current credentials</a></td>' +
          '<td>Revoke the access token associated with the current user ' +
          '    session. After revoking credentials, if you go to the test ' +
          '    page, you should see an <code>invalid_grant</code> error.' +
          '</td></tr>' +
          '<tr><td><a href="/clear">Clear Flask session credentials</a></td>' +
          '<td>Clear the access token currently stored in the user session. ' +
          '    After clearing the token, if you <a href="/test">test the ' +
          '    API request</a> again, you should go back to the auth flow.' +
          '</td></tr></table>')

if __name__ == '__main__':
  # When running locally, disable OAuthlib's HTTPs verification.
  # ACTION ITEM for developers:
  #     When running in production *do not* leave this option enabled.
  os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

  # This disables the requested scopes and granted scopes check.
  # If users only grant partial request, the warning would not be thrown.
  os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1'

  # Specify a hostname and port that are set as a valid redirect URI
  # for your API project in the Google API Console.
  app.run('localhost', 8080, debug=True)

Ruby

この例では、Sinatra フレームワークを使用します。

require 'googleauth'
require 'googleauth/web_user_authorizer'
require 'googleauth/stores/redis_token_store'

require 'google/apis/drive_v3'
require 'google/apis/calendar_v3'

require 'sinatra'

configure do
  enable :sessions

  # Required, call the from_file method to retrieve the client ID from a
  # client_secret.json file.
  set :client_id, Google::Auth::ClientId.from_file('/path/to/client_secret.json')

  # Required, scope value
  # Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar.
  scope = ['Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY',
           'Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY']

  # Required, Authorizers require a storage instance to manage long term persistence of
  # access and refresh tokens.
  set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)

  # Required, indicate where the API server will redirect the user after the user completes
  # the authorization flow. The redirect URI is required. The value must exactly
  # match one of the authorized redirect URIs for the OAuth 2.0 client, which you
  # configured in the API Console. If this value doesn't match an authorized URI,
  # you will get a 'redirect_uri_mismatch' error.
  set :callback_uri, '/oauth2callback'

  # To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI
  # from the client_secret.json file. To get these credentials for your application, visit
  # https://console.cloud.google.com/apis/credentials.
  set :authorizer, Google::Auth::WebUserAuthorizer.new(settings.client_id, settings.scope,
                          settings.token_store, callback_uri: settings.callback_uri)
end

get '/' do
  # NOTE: Assumes the user is already authenticated to the app
  user_id = request.session['user_id']

  # Fetch stored credentials for the user from the given request session.
  # nil if none present
  credentials = settings.authorizer.get_credentials(user_id, request)

  if credentials.nil?
    # Generate a url that asks the user to authorize requested scope(s).
    # Then, redirect user to the url.
    redirect settings.authorizer.get_authorization_url(request: request)
  end
  
  # User authorized the request. Now, check which scopes were granted.
  if credentials.scope.include?(Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY)
    # User authorized read-only Drive activity permission.
    # Example of using Google Drive API to list filenames in user's Drive.
    drive = Google::Apis::DriveV3::DriveService.new
    files = drive.list_files(options: { authorization: credentials })
    "<pre>#{JSON.pretty_generate(files.to_h)}</pre>"
  else
    # User didn't authorize read-only Drive activity permission.
    # Update UX and application accordingly
  end

  # Check if user authorized Calendar read permission.
  if credentials.scope.include?(Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY)
    # User authorized Calendar read permission.
    # Calling the APIs, etc.
  else
    # User didn't authorize Calendar read permission.
    # Update UX and application accordingly
  end
end

# Receive the callback from Google's OAuth 2.0 server.
get '/oauth2callback' do
  # Handle the result of the oauth callback. Defers the exchange of the code by
  # temporarily stashing the results in the user's session.
  target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request)
  redirect target_url
end

Node.js

この例を実行するには:

  1. API Consoleで、ローカル マシンの URL をリダイレクト URL のリストに追加します。たとえば、http://localhost を追加します。
  2. Node.js のメンテナンス LTS、アクティブ LTS、または現在のリリースがインストールされていることを確認します。
  3. 新しいディレクトリを作成し、そのディレクトリに移動します。次に例を示します。
    mkdir ~/nodejs-oauth2-example
    cd ~/nodejs-oauth2-example
  4. npm を使用して、Node.js 用の Google API クライアント ライブラリをインストールします。
    npm install googleapis
  5. 次の内容の main.js ファイルを作成します。
  6. 例を実行します。
    node .\main.js

main.js

const http = require('http');
const https = require('https');
const url = require('url');
const { google } = require('googleapis');
const crypto = require('crypto');
const express = require('express');
const session = require('express-session');

/**
 * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI.
 * To get these credentials for your application, visit
 * https://console.cloud.google.com/apis/credentials.
 */
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly',
  'https://www.googleapis.com/auth/calendar.readonly'
];

/* Global variable that stores user credential in this code example.
 * ACTION ITEM for developers:
 *   Store user's refresh token in your data store if
 *   incorporating this code into your real app.
 *   For more information on handling refresh tokens,
 *   see https://github.com/googleapis/google-api-nodejs-client#handling-refresh-tokens
 */
let userCredential = null;

async function main() {
  const app = express();

  app.use(session({
    secret: 'your_secure_secret_key', // Replace with a strong secret
    resave: false,
    saveUninitialized: false,
  }));

  // Example on redirecting user to Google's OAuth 2.0 server.
  app.get('/', async (req, res) => {
    // Generate a secure random state value.
    const state = crypto.randomBytes(32).toString('hex');
    // Store state in the session
    req.session.state = state;

    // Generate a url that asks permissions for the Drive activity and Google Calendar scope
    const authorizationUrl = oauth2Client.generateAuthUrl({
      // 'online' (default) or 'offline' (gets refresh_token)
      access_type: 'offline',
      /** Pass in the scopes array defined above.
        * Alternatively, if only one scope is needed, you can pass a scope URL as a string */
      scope: scopes,
      // Enable incremental authorization. Recommended as a best practice.
      include_granted_scopes: true,
      // Include the state parameter to reduce the risk of CSRF attacks.
      state: state
    });

    res.redirect(authorizationUrl);
  });

  // Receive the callback from Google's OAuth 2.0 server.
  app.get('/oauth2callback', async (req, res) => {
    // Handle the OAuth 2.0 server response
    let q = url.parse(req.url, true).query;

    if (q.error) { // An error response e.g. error=access_denied
      console.log('Error:' + q.error);
    } else if (q.state !== req.session.state) { //check state value
      console.log('State mismatch. Possible CSRF attack');
      res.end('State mismatch. Possible CSRF attack');
    } else { // Get access and refresh tokens (if access_type is offline)
      let { tokens } = await oauth2Client.getToken(q.code);
      oauth2Client.setCredentials(tokens);

      /** Save credential to the global variable in case access token was refreshed.
        * ACTION ITEM: In a production app, you likely want to save the refresh token
        *              in a secure persistent database instead. */
      userCredential = tokens;
      
      // User authorized the request. Now, check which scopes were granted.
      if (tokens.scope.includes('https://www.googleapis.com/auth/drive.metadata.readonly'))
      {
        // User authorized read-only Drive activity permission.
        // 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) => {
          if (err1) return console.log('The API returned an error: ' + err1);
          const files = res1.data.files;
          if (files.length) {
            console.log('Files:');
            files.map((file) => {
              console.log(`${file.name} (${file.id})`);
            });
          } else {
            console.log('No files found.');
          }
        });
      }
      else
      {
        // User didn't authorize read-only Drive activity permission.
        // Update UX and application accordingly
      }

      // Check if user authorized Calendar read permission.
      if (tokens.scope.includes('https://www.googleapis.com/auth/calendar.readonly'))
      {
        // User authorized Calendar read permission.
        // Calling the APIs, etc.
      }
      else
      {
        // User didn't authorize Calendar read permission.
        // Update UX and application accordingly
      }
    }
  });

  // Example on revoking a token
  app.get('/revoke', async (req, res) => {
    // Build the string for the POST request
    let postData = "token=" + userCredential.access_token;

    // Options for POST request to Google's OAuth 2.0 server to revoke a token
    let postOptions = {
      host: 'oauth2.googleapis.com',
      port: '443',
      path: '/revoke',
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(postData)
      }
    };

    // Set up the request
    const postReq = https.request(postOptions, function (res) {
      res.setEncoding('utf8');
      res.on('data', d => {
        console.log('Response: ' + d);
      });
    });

    postReq.on('error', error => {
      console.log(error)
    });

    // Post the request with data
    postReq.write(postData);
    postReq.end();
  });


  const server = http.createServer(app);
  server.listen(8080);
}
main().catch(console.error);

HTTP/REST

この Python の例では、Flask フレームワークと Requests ライブラリを使用して、OAuth 2.0 ウェブフローを示します。このフローには、Python 用 Google API クライアント ライブラリを使用することをおすすめします。([Python] タブの例では、クライアント ライブラリを使用しています)。

import json
import flask
import requests

app = flask.Flask(__name__)

# To get these credentials (CLIENT_ID CLIENT_SECRET) and for your application, visit
# https://console.cloud.google.com/apis/credentials.
CLIENT_ID = '123456789.apps.googleusercontent.com'
CLIENT_SECRET = 'abc123'  # Read from a file or environmental variable in a real app

# Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar.
SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly'

# Indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required. The value must exactly
# match one of the authorized redirect URIs for the OAuth 2.0 client, which you
# configured in the API Console. If this value doesn't match an authorized URI,
# you will get a 'redirect_uri_mismatch' error.
REDIRECT_URI = 'http://example.com/oauth2callback'

@app.route('/')
def index():
  if 'credentials' not in flask.session:
    return flask.redirect(flask.url_for('oauth2callback'))

  credentials = json.loads(flask.session['credentials'])

  if credentials['expires_in'] <= 0:
    return flask.redirect(flask.url_for('oauth2callback'))
  else: 
    # User authorized the request. Now, check which scopes were granted.
    if 'https://www.googleapis.com/auth/drive.metadata.readonly' in credentials['scope']:
      # User authorized read-only Drive activity permission.
      # Example of using Google Drive API to list filenames in user's Drive.
      headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])}
      req_uri = 'https://www.googleapis.com/drive/v2/files'
      r = requests.get(req_uri, headers=headers).text
    else:
      # User didn't authorize read-only Drive activity permission.
      # Update UX and application accordingly
      r = 'User did not authorize Drive permission.'

    # Check if user authorized Calendar read permission.
    if 'https://www.googleapis.com/auth/calendar.readonly' in credentials['scope']:
      # User authorized Calendar read permission.
      # Calling the APIs, etc.
      r += 'User authorized Calendar permission.'
    else:
      # User didn't authorize Calendar read permission.
      # Update UX and application accordingly
      r += 'User did not authorize Calendar permission.'

  return r

@app.route('/oauth2callback')
def oauth2callback():
  if 'code' not in flask.request.args:
    state = str(uuid.uuid4())
    flask.session['state'] = state
    # Generate a url that asks permissions for the Drive activity
    # and Google Calendar scope. Then, redirect user to the url.
    auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
                '&client_id={}&redirect_uri={}&scope={}&state={}').format(CLIENT_ID, REDIRECT_URI,
                                                                          SCOPE, state)
    return flask.redirect(auth_uri)
  else:
    if 'state' not in flask.request.args or flask.request.args['state'] != flask.session['state']:
      return 'State mismatch. Possible CSRF attack.', 400

    auth_code = flask.request.args.get('code')
    data = {'code': auth_code,
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET,
            'redirect_uri': REDIRECT_URI,
            'grant_type': 'authorization_code'}

    # Exchange authorization code for access and refresh tokens (if access_type is offline)
    r = requests.post('https://oauth2.googleapis.com/token', data=data)
    flask.session['credentials'] = r.text
    return flask.redirect(flask.url_for('index'))

if __name__ == '__main__':
  import uuid
  app.secret_key = str(uuid.uuid4())
  app.debug = False
  app.run()

リダイレクト URI の検証ルール

Google は、デベロッパーがアプリケーションの安全性を維持できるよう、リダイレクト URI に次の検証ルールを適用します。リダイレクト URI は、次のルールに準拠している必要があります。以下で説明するドメイン、ホスト、パス、クエリ、スキーム、ユーザー情報の定義については、RFC 3986 セクション 3 をご覧ください。

検証規則
スキーム

リダイレクト URI には、プレーン HTTP ではなく HTTPS スキームを使用する必要があります。ローカルホスト URI(ローカルホスト IP アドレス URI を含む)はこのルールの対象外です。

ホスト

ホストに未加工の IP アドレスを指定することはできません。ローカルホストの IP アドレスはこのルールの対象外です。

ドメイン
  • ホスト TLD(トップレベル ドメイン)は、パブリック サフィックス リストに属している必要があります。
  • ホスト ドメインを “googleusercontent.com” にすることはできません。
  • アプリがドメインを所有している場合を除き、リダイレクト URI に短縮 URL ドメイン(goo.gl など)を含めることはできません。また、短縮ドメインを所有するアプリがそのドメインにリダイレクトする場合、そのリダイレクト URI のパスに “/google-callback/” を含むか、末尾が “/google-callback” でなければなりません。
  • Userinfo

    リダイレクト URI に userinfo サブコンポーネントを含めることはできません。

    [Path]

    リダイレクト URI には、“/..”“\..”、またはそれらの URL エンコードで表されるパス トラバーサル(ディレクトリ バックトラッキングとも呼ばれます)を含めることはできません。

    クエリ

    リダイレクト URI にオープン リダイレクトを含めることはできません。

    Fragment

    リダイレクト URI にフラグメント コンポーネントを含めることはできません。

    文字数 リダイレクト URI には、次の文字を含めることはできません。
    • ワイルドカード文字('*'
    • 印刷不可能な ASCII 文字
    • 無効なパーセント エンコード(パーセント記号の後に 2 桁の 16 進数が続く URL エンコード形式に従っていないパーセント エンコード)
    • NULL 文字(エンコードされた NULL 文字。例: %00%C0%80

    段階的な認可

    OAuth 2.0 プロトコルでは、アプリはリソースへのアクセス権限をリクエストします。リソースはスコープによって識別されます。リソースの承認は、必要なときにリクエストすることがユーザー エクスペリエンスのベスト プラクティスとされています。この方法を有効にするため、Google の認可サーバーは段階的な認可をサポートしています。この機能を使用すると、必要なときにスコープをリクエストできます。ユーザーが新しいスコープの権限を付与すると、ユーザーがプロジェクトに付与したすべてのスコープを含むトークンと交換できる認証コードが返されます。

    たとえば、音楽トラックを試聴してミックスを作成できるアプリでは、ログイン時に必要なリソースはごくわずかかもしれません。ログインするユーザーの名前だけが必要な場合もあります。ただし、完成したミックスを保存するには、そのユーザーの Google ドライブにアクセスする必要があります。ほとんどのユーザーは、アプリが実際に Google ドライブへのアクセスを必要とするタイミングでのみアクセス権限を求められることを自然に感じます。

    この場合、アプリはログイン時に openid スコープと profile スコープをリクエストして基本的なログインを行い、その後、最初のミックス保存リクエスト時に https://www.googleapis.com/auth/drive.file スコープをリクエストする可能性があります。

    増分認証を実装するには、アクセス トークンをリクエストする通常のフローを完了しますが、認証リクエストに以前に付与されたスコープが含まれていることを確認します。このアプローチにより、アプリは複数のアクセス トークンを管理する必要がなくなります。

    増分認可で取得したアクセス トークンには、次のルールが適用されます。

    • このトークンを使用して、新しい統合認可にロールアップされたスコープに対応するリソースにアクセスできます。
    • 結合された承認の更新トークンを使用してアクセス トークンを取得すると、アクセス トークンは結合された承認を表し、レスポンスに含まれる scope 値のいずれにも使用できます。
    • 統合された認可には、ユーザーが API プロジェクトに付与したすべてのスコープが含まれます。付与が異なるクライアントからリクエストされた場合でも同様です。たとえば、ユーザーがアプリケーションのデスクトップ クライアントを使用して 1 つのスコープへのアクセスを許可し、モバイル クライアントを使用して同じアプリケーションに別のスコープを許可した場合、統合された認可には両方のスコープが含まれます。
    • 結合された認可を表すトークンを取り消すと、関連付けられたユーザーに代わって、その認可のすべてのスコープへのアクセスが同時に取り消されます。

    ステップ 1: 認可パラメータを設定するの言語固有のコードサンプルと、ステップ 2: Google の OAuth 2.0 サーバーにリダイレクトするの HTTP/REST リダイレクト URL のサンプルは、すべて増分認可を使用しています。以下のコードサンプルは、増分認証を使用するために追加する必要があるコードも示しています。

    PHP

    $client->setIncludeGrantedScopes(true);

    Python

    Python では、include_granted_scopes キーワード引数を true に設定して、承認リクエストに以前に付与されたスコープが含まれるようにします。次の例に示すように、include_granted_scopes が設定する唯一のキーワード引数ではない可能性は十分にあります。

    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='true')

    Ruby

    auth_client.update!(
      :additional_parameters => {"include_granted_scopes" => "true"}
    )

    Node.js

    const authorizationUrl = oauth2Client.generateAuthUrl({
      // 'online' (default) or 'offline' (gets refresh_token)
      access_type: 'offline',
      /** Pass in the scopes array defined above.
        * Alternatively, if only one scope is needed, you can pass a scope URL as a string */
      scope: scopes,
      // Enable incremental authorization. Recommended as a best practice.
      include_granted_scopes: true
    });

    HTTP/REST

    GET https://accounts.google.com/o/oauth2/v2/auth?
      client_id=your_client_id&
      response_type=code&
      state=state_parameter_passthrough_value&
      scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly&
      redirect_uri=https%3A//oauth2.example.com/code&
      prompt=consent&
      include_granted_scopes=true

    アクセス トークンの更新(オフライン アクセス)

    アクセス トークンは定期的に期限が切れ、関連する API リクエストに対して無効な認証情報になります。トークンに関連付けられたスコープへのオフライン アクセスをリクエストした場合は、ユーザーに権限を求めることなく(ユーザーが不在の場合を含めて)アクセス トークンを更新できます。

    • Google API クライアント ライブラリを使用する場合、オフライン アクセス用にオブジェクトを構成していれば、クライアント オブジェクトは必要に応じてアクセス トークンを更新します。
    • クライアント ライブラリを使用していない場合は、ユーザーを Google の OAuth 2.0 サーバーにリダイレクトするときに、access_type HTTP クエリ パラメータを offline に設定する必要があります。この場合、Google の認可サーバーは、アクセス トークンの認可コードを交換するときに更新トークンを返します。その後、アクセス トークンの有効期限が切れた場合(またはその他の場合)、更新トークンを使用して新しいアクセス トークンを取得できます。

    ユーザーがいないときに Google API にアクセスする必要があるアプリケーションには、オフライン アクセスのリクエストが必須です。たとえば、バックアップ サービスを実行するアプリや、あらかじめ決められた時間にアクションを実行するアプリは、ユーザーがいないときにアクセス トークンを更新できる必要があります。アクセス権のデフォルトのスタイルは online と呼ばれます。

    サーバーサイド ウェブ アプリケーション、インストール済みアプリケーション、デバイスはすべて、認可プロセス中に更新トークンを取得します。通常、更新トークンはクライアントサイド(JavaScript)のウェブ アプリケーションでは使用されません。

    PHP

    アプリケーションで Google API へのオフライン アクセスが必要な場合は、API クライアントのアクセスタイプを offline に設定します。

    $client->setAccessType("offline");

    ユーザーがリクエストされたスコープへのオフライン アクセスを許可すると、ユーザーがオフラインのときに API クライアントを使用してユーザーの代わりに Google API にアクセスできるようになります。クライアント オブジェクトは、必要に応じてアクセス トークンを更新します。

    Python

    Python で、access_type キーワード引数を offline に設定して、ユーザーに権限の再確認を求めることなくアクセス トークンを更新できるようにします。次の例に示すように、access_type が設定するキーワード引数の中で唯一のものではない可能性は十分にあります。

    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='true')

    ユーザーがリクエストされたスコープへのオフライン アクセスを許可すると、ユーザーがオフラインのときに API クライアントを使用してユーザーの代わりに Google API にアクセスできるようになります。クライアント オブジェクトは、必要に応じてアクセス トークンを更新します。

    Ruby

    アプリケーションで Google API へのオフライン アクセスが必要な場合は、API クライアントのアクセスタイプを offline に設定します。

    auth_client.update!(
      :additional_parameters => {"access_type" => "offline"}
    )

    ユーザーがリクエストされたスコープへのオフライン アクセスを許可すると、ユーザーがオフラインのときに API クライアントを使用してユーザーの代わりに Google API にアクセスできるようになります。クライアント オブジェクトは、必要に応じてアクセス トークンを更新します。

    Node.js

    アプリケーションで Google API へのオフライン アクセスが必要な場合は、API クライアントのアクセスタイプを offline に設定します。

    const authorizationUrl = oauth2Client.generateAuthUrl({
      // 'online' (default) or 'offline' (gets refresh_token)
      access_type: 'offline',
      /** Pass in the scopes array defined above.
        * Alternatively, if only one scope is needed, you can pass a scope URL as a string */
      scope: scopes,
      // Enable incremental authorization. Recommended as a best practice.
      include_granted_scopes: true
    });

    ユーザーがリクエストされたスコープへのオフライン アクセスを許可すると、ユーザーがオフラインのときに API クライアントを使用してユーザーの代わりに Google API にアクセスできるようになります。クライアント オブジェクトは、必要に応じてアクセス トークンを更新します。

    アクセス トークンは期限切れになります。このライブラリは、有効期限が近づくと、更新トークンを自動的に使用して新しいアクセス トークンを取得します。常に最新のトークンを保存する簡単な方法は、トークン イベントを使用することです。

    oauth2Client.on('tokens', (tokens) => {
      if (tokens.refresh_token) {
        // store the refresh_token in your secure persistent database
        console.log(tokens.refresh_token);
      }
      console.log(tokens.access_token);
    });

    このトークン イベントは最初の認証でのみ発生します。更新トークンを受け取るには、generateAuthUrl メソッドを呼び出すときに access_typeoffline に設定しておく必要があります。更新トークンを受け取るための適切な制約を設定せずに、アプリに必要な権限をすでに付与している場合は、新しい更新トークンを受け取るためにアプリを再認証する必要があります。

    refresh_token を後で設定するには、setCredentials メソッドを使用します。

    oauth2Client.setCredentials({
      refresh_token: `STORED_REFRESH_TOKEN`
    });

    クライアントが更新トークンを取得すると、API の次の呼び出しでアクセス トークンが自動的に取得され、更新されます。

    HTTP/REST

    アクセス トークンを更新するには、アプリケーションが次のパラメータを含む HTTPS POST リクエストを Google の認証サーバー(https://oauth2.googleapis.com/token)に送信します。

    フィールド
    client_id API Consoleから取得したクライアント ID。
    client_secret API Consoleから取得したクライアント シークレット。
    grant_type OAuth 2.0 仕様で定義されているように、このフィールドの値は refresh_token に設定する必要があります。
    refresh_token 認証コード交換から返された更新トークン。

    次のスニペットは、リクエストの例を示しています。

    POST /token HTTP/1.1
    Host: oauth2.googleapis.com
    Content-Type: application/x-www-form-urlencoded
    
    client_id=your_client_id&
    client_secret=your_client_secret&
    refresh_token=refresh_token&
    grant_type=refresh_token

    ユーザーがアプリケーションに付与したアクセス権を取り消していない限り、トークン サーバーは新しいアクセス トークンを含む JSON オブジェクトを返します。次のスニペットは、サンプル レスポンスを示しています。

    {
      "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
      "expires_in": 3920,
      "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly",
      "token_type": "Bearer"
    }

    発行される更新トークンの数には上限があります。クライアントとユーザーの組み合わせごとに 1 つ、すべてのクライアントのユーザーごとに 1 つです。更新トークンは長期ストレージに保存し、有効な限り使用し続ける必要があります。アプリケーションが過剰な数の更新トークンをリクエストすると、これらの上限に達する可能性があります。その場合、古い更新トークンは機能しなくなります。

    トークンの取り消し

    ユーザーがアプリに付与したアクセス権を取り消したい場合もあります。ユーザーは、 アカウント設定にアクセスしてアクセス権を取り消すことができます。詳しくは、アカウントにアクセスできるサードパーティのサイトやアプリのサポート ドキュメントの「サイトやアプリのアクセス権を削除する」セクションをご覧ください。

    アプリケーションが、自身に付与されたアクセス権をプログラムで取り消すことも可能です。プログラムによる取り消しは、ユーザーが登録を解除した場合、アプリを削除した場合、アプリに必要な API リソースが大幅に変更された場合などに重要です。つまり、削除プロセスの一部として、以前にアプリに付与された権限が削除されるように API リクエストを含めることができます。

    PHP

    プログラムでトークンを取り消すには、revokeToken() を呼び出します。

    $client->revokeToken();

    Python

    プログラムでトークンを取り消すには、トークンをパラメータとして含み、Content-Type ヘッダーを設定する https://oauth2.googleapis.com/revoke へのリクエストを行います。

    requests.post('https://oauth2.googleapis.com/revoke',
        params={'token': credentials.token},
        headers = {'content-type': 'application/x-www-form-urlencoded'})

    Ruby

    プログラムでトークンを取り消すには、oauth2.revoke エンドポイントに HTTP リクエストを送信します。

    uri = URI('https://oauth2.googleapis.com/revoke')
    response = Net::HTTP.post_form(uri, 'token' => auth_client.access_token)

    トークンはアクセス トークンまたは更新トークンです。トークンがアクセス トークンで、対応する更新トークンがある場合、更新トークンも取り消されます。

    取り消しが正常に処理されると、レスポンスのステータス コードは 200 になります。エラー条件の場合、ステータス コード 400 がエラーコードとともに返されます。

    Node.js

    トークンをプログラムで取り消すには、/revoke エンドポイントに HTTPS POST リクエストを送信します。

    const https = require('https');
    
    // Build the string for the POST request
    let postData = "token=" + userCredential.access_token;
    
    // Options for POST request to Google's OAuth 2.0 server to revoke a token
    let postOptions = {
      host: 'oauth2.googleapis.com',
      port: '443',
      path: '/revoke',
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(postData)
      }
    };
    
    // Set up the request
    const postReq = https.request(postOptions, function (res) {
      res.setEncoding('utf8');
      res.on('data', d => {
        console.log('Response: ' + d);
      });
    });
    
    postReq.on('error', error => {
      console.log(error)
    });
    
    // Post the request with data
    postReq.write(postData);
    postReq.end();

    token パラメータは、アクセス トークンまたは更新トークンにできます。トークンがアクセス トークンで、対応する更新トークンがある場合、更新トークンも取り消されます。

    取り消しが正常に処理されると、レスポンスのステータス コードは 200 になります。エラー条件の場合、ステータス コード 400 がエラーコードとともに返されます。

    HTTP/REST

    プログラムでトークンを取り消すには、アプリケーションが https://oauth2.googleapis.com/revoke にリクエストを送信し、トークンをパラメータとして含めます。

    curl -d -X -POST --header "Content-type:application/x-www-form-urlencoded" \
            https://oauth2.googleapis.com/revoke?token={token}

    トークンはアクセス トークンまたは更新トークンです。トークンがアクセス トークンで、対応する更新トークンがある場合、更新トークンも取り消されます。

    取り消しが正常に処理されると、レスポンスの HTTP ステータス コードは 200 になります。エラー条件の場合、エラーコードとともに HTTP ステータス コード 400 が返されます。

    時間に基づくアクセス

    時間ベースのアクセスでは、ユーザーはアクションを完了するために、アプリが期間限定でデータにアクセスすることを許可できます。時間ベースのアクセスは、同意フロー中に一部の Google サービスで利用できます。ユーザーは、限られた期間のみアクセスを許可するオプションを選択できます。たとえば、 Data Portability API を使用すると、データを 1 回だけ転送できます。

    ユーザーがアプリに時間ベースのアクセス権を付与すると、更新トークンは指定された期間の経過後に期限切れになります。特定の状況下では、更新トークンが早期に無効になることがあります。詳しくは、こちらのケースをご覧ください。このような場合、認可コード交換レスポンスで返される refresh_token_expires_in フィールドは、更新トークンの有効期限が切れるまでの残り時間を表します。

    クロスアカウント保護機能の実装

    ユーザーのアカウントを保護するために行うべき追加の手順として、Google のクロス アカウント保護サービスを利用してクロス アカウント保護を実装することが挙げられます。このサービスでは、ユーザー アカウントの大きな変更に関する情報をアプリケーションに提供するセキュリティ イベント通知を登録できます。この情報を使用して、イベントへの対応方法に応じてアクションを実行できます。

    Google のクロス アカウント保護サービスからアプリに送信されるイベントタイプの例を次に示します。

    • https://schemas.openid.net/secevent/risc/event-type/sessions-revoked
    • https://schemas.openid.net/secevent/oauth/event-type/token-revoked
    • https://schemas.openid.net/secevent/risc/event-type/account-disabled

    クロスアカウント保護の実装方法と利用可能なイベントの完全なリストについては、 クロスアカウント保護でユーザー アカウントを保護する をご覧ください。