クライアントサイド ウェブ アプリケーション用の OAuth 2.0

このドキュメントでは、JavaScript ウェブ アプリケーションから Google API にアクセスするための OAuth 2.0 認可を実装する方法について説明します。OAuth 2.0 では、ユーザー名やパスワードなどの情報を秘密にしたまま、ユーザーが特定のデータをアプリケーションと共有できます。たとえば、アプリケーションで OAuth 2.0 を使い、Google ドライブにファイルを保存する許可をユーザーから取得できます。

この OAuth 2.0 フローは、暗黙的付与フローと呼ばれます。これは、ユーザーがアプリケーションに存在している間のみ API にアクセスするアプリケーション向けに設計されています。これらのアプリは機密情報を保存できません。

このフローでは、アプリが Google URL を開き、クエリ パラメータを使用してアプリと、アプリに必要な API アクセスの種類を識別します。URL は、現在のブラウザ ウィンドウまたはポップアップで開くことができます。ユーザーは Google で認証し、リクエストされた権限を付与できます。その後、Google はユーザーをアプリにリダイレクトします。リダイレクトにはアクセス トークンが含まれており、アプリはこれを検証して API リクエストの送信に使用します。

Google API クライアント ライブラリと Google Identity Services

JavaScript 用の Google API クライアント ライブラリを使用して Google に承認済みの呼び出しを行う場合は、 Google Identity Services JavaScript ライブラリを使用して OAuth 2.0 フローを処理する必要があります。OAuth 2.0 の暗黙的な付与フローをベースとする Google Identity Services のトークンモデルをご覧ください。

前提条件

プロジェクトで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. [認証情報を作成] > [OAuth クライアント ID] をクリックします。
  3. アプリケーションの種類として [ウェブ アプリケーション] を選択します。
  4. フォームに入力します。JavaScript を使用して承認済みの Google API リクエストを行うアプリケーションは、承認済みの JavaScript 生成元を指定する必要があります。オリジンは、アプリケーションが OAuth 2.0 サーバーにリクエストを送信できるドメインを識別します。これらのオリジンは、Google の検証ルールに準拠している必要があります。

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

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

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

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

OAuth 2.0 アクセス トークンの取得

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

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

ユーザーのデータにアクセスする権限をリクエストするには、ユーザーを Google の OAuth 2.0 サーバーにリダイレクトします。

OAuth 2.0 エンドポイント

Google の OAuth 2.0 エンドポイント(https://accounts.google.com/o/oauth2/v2/auth)からアクセスをリクエストする URL を生成します。このエンドポイントには HTTPS 経由でアクセスできます。プレーン HTTP 接続は拒否されます。

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

パラメータ
client_id 必須

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

redirect_uri 必須

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

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

response_type 必須

JavaScript アプリケーションでは、パラメータの値を token に設定する必要があります。この値は、認可プロセスの完了後にユーザーがリダイレクトされる URI(#)のフラムメント識別子で、アクセス トークンを name=value ペアとして返すように Google 認可サーバーに指示します。

scope 必須

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

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

可能な限り、アプリケーションでコンテキスト内の認可スコープへのアクセスをリクエストすることをおすすめします。段階的認証を使用して、コンテキストに沿ってユーザーデータへのアクセス権をリクエストすると、アプリがリクエストしているアクセス権が必要な理由をユーザーが理解しやすくなります。

state 推奨

認可リクエストと認可サーバーのレスポンス間で状態を維持するために、アプリケーションが使用する文字列値を指定します。ユーザーがアプリのアクセス リクエストに同意または拒否すると、サーバーは redirect_uri の URL フラグメント ID(#)で name=value ペアとして送信された正確な値を返します。

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

include_granted_scopes 省略可

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

login_hint 省略可

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

パラメータ値をメールアドレスまたは sub ID(ユーザーの Google ID と同等)に設定します。

prompt 省略可

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

次の値があります。

none 認証画面や同意画面は表示しないでください。他の値で指定しないでください。
consent ユーザーに同意を求めるメッセージが表示されます。
select_account アカウントを選択するようユーザーに求める。

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&
 include_granted_scopes=true&
 response_type=token&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//oauth2.example.com/code&
 client_id=client_id

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

JavaScript サンプルコード

次の JavaScript スニペットは、JavaScript 用の Google APIs クライアント ライブラリを使用せずに JavaScript で認可フローを開始する方法を示しています。この OAuth 2.0 エンドポイントはクロスオリジン リソース シェアリング(CORS)をサポートしていないため、このスニペットは、そのエンドポイントへのリクエストを開くフォームを作成します。

/*
 * Create form to request access token from Google's OAuth 2.0 server.
 */
function oauthSignIn() {
  // Google's OAuth 2.0 endpoint for requesting an access token
  var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';

  // Create <form> element to submit parameters to OAuth 2.0 endpoint.
  var form = document.createElement('form');
  form.setAttribute('method', 'GET'); // Send as a GET request.
  form.setAttribute('action', oauth2Endpoint);

  // Parameters to pass to OAuth 2.0 endpoint.
  var params = {'client_id': 'YOUR_CLIENT_ID',
                'redirect_uri': 'YOUR_REDIRECT_URI',
                'response_type': 'token',
                'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly',
                'include_granted_scopes': 'true',
                'state': 'pass-through value'};

  // Add form parameters as hidden input values.
  for (var p in params) {
    var input = document.createElement('input');
    input.setAttribute('type', 'hidden');
    input.setAttribute('name', p);
    input.setAttribute('value', params[p]);
    form.appendChild(input);
  }

  // Add form to page and submit it to open the OAuth 2.0 endpoint.
  document.body.appendChild(form);
  form.submit();
}

ステップ 2: 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 で認可リクエストを開くと、このエラー メッセージが表示されることがあります。代わりに、Android 版 Google ログインや OpenID Foundation の Android 版 AppAuth などの Android ライブラリを使用してください。

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

iOS

iOS と macOS のデベロッパーは、WKWebView で承認リクエストを開いたときに、このエラーが発生することがあります。デベロッパーは、代わりに Google ログイン 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

リクエストの送信元が、このクライアントに対して承認されていません。origin_mismatch をご覧ください。

invalid_grant

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

origin_mismatch

認可リクエストを発信した JavaScript のスキーマ、ドメイン、ポートが、OAuth クライアント ID に登録されている承認済みの JavaScript 生成元 URI と一致していない。 Google API Console Credentials pageで、承認済みの JavaScript 生成元を確認します。

redirect_uri_mismatch

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

認可リクエストを発信した JavaScript のスキーマ、ドメイン、ポートが、OAuth クライアント ID に登録されている承認済みの JavaScript 生成元 URI と一致していない。 Google API Console Credentials pageで、承認済みの JavaScript 生成元を確認します。

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

invalid_request

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

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

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

OAuth 2.0 エンドポイント

OAuth 2.0 サーバーは、アクセス トークン リクエストで指定された redirect_uri にレスポンスを送信します。

ユーザーがリクエストを承認すると、レスポンスにアクセス トークンが格納されます。ユーザーがリクエストを承認しないと、レスポンスにエラー メッセージが格納されます。アクセス トークンまたはエラー メッセージは、次のようにリダイレクト URI のハッシュ フラグメントで返されます。

  • アクセス トークンのレスポンス:

    https://oauth2.example.com/callback#access_token=4/P7q7W91&token_type=Bearer&expires_in=3600

    フラグメント文字列には、access_token パラメータに加えて、常に Bearer に設定される token_type パラメータと、トークンの有効期間(秒単位)を指定する expires_in パラメータも含まれています。アクセス トークン リクエストで state パラメータが指定されている場合、その値もレスポンスに含まれます。

  • エラー レスポンス:
    https://oauth2.example.com/callback#error=access_denied

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&
 include_granted_scopes=true&
 response_type=token&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//oauth2.example.com/code&
 client_id=client_id

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

ステップ 4: ユーザーが付与したスコープを確認する

複数のスコープを一度にリクエストする場合、ユーザーがアプリがリクエストするすべてのスコープを許可しないことがあります。アプリは、ユーザーによって付与されたスコープを常に確認し、スコープの拒否が発生した場合は関連する機能を無効にして対処する必要があります。詳細については、きめ細かい権限を処理する方法をご覧ください。

OAuth 2.0 エンドポイント

ユーザーがアプリケーションに特定のスコープへのアクセス権を付与しているかどうかを確認するには、アクセス トークン レスポンスの 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 の呼び出し

OAuth 2.0 エンドポイント

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

すべての Google API を試して、そのスコープを確認するには、OAuth 2.0 Playground をご覧ください。

HTTP GET の例

Authorization: Bearer HTTP ヘッダーを使用して drive.files エンドポイント(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

JavaScript サンプルコード

次のコード スニペットは、CORS(クロスオリジン リソース シェアリング)を使用して Google API にリクエストを送信する方法を示しています。この例では、JavaScript 用の Google API クライアント ライブラリは使用しません。ただし、クライアント ライブラリを使用していない場合でも、そのライブラリのドキュメントの CORS サポートガイドは、これらのリクエストを理解するうえで役立つ可能性があります。

このコード スニペットでは、access_token 変数は、承認済みユーザーに代わって API リクエストを行うために取得したトークンを表します。完全な例では、そのトークンをブラウザのローカル ストレージに保存し、API リクエストの実行時に取得する方法を示します。

var xhr = new XMLHttpRequest();
xhr.open('GET',
    'https://www.googleapis.com/drive/v3/about?fields=user&' +
    'access_token=' + params['access_token']);
xhr.onreadystatechange = function (e) {
  console.log(xhr.response);
};
xhr.send(null);

サンプルコードの全文

OAuth 2.0 エンドポイント

このコードサンプルは、JavaScript 用の Google API クライアント ライブラリを使用せずに JavaScript で OAuth 2.0 フローを完了する方法を示しています。このコードは、API リクエストを試すためのボタンを表示する HTML ページ用です。ボタンをクリックすると、ページがブラウザのローカル ストレージに API アクセス トークンを保存しているかどうかがコードによって確認されます。有効な場合は、API リクエストを実行します。それ以外の場合は、OAuth 2.0 フローを開始します。

OAuth 2.0 フローの場合、ページは次の手順で処理されます。

  1. ユーザーを Google の OAuth 2.0 サーバーにリダイレクトし、https://www.googleapis.com/auth/drive.metadata.readonly スコープと https://www.googleapis.com/auth/calendar.readonly スコープへのアクセスをリクエストします。
  2. リクエストされた 1 つ以上のスコープへのアクセスを許可(または拒否)すると、ユーザーは元のページにリダイレクトされます。このページでは、フラグメント識別子文字列からアクセス トークンが解析されます。
  3. このページでは、ユーザーがアプリケーションへのアクセス権を付与したスコープを確認できます。
  4. ユーザーがリクエストされた scope() へのアクセスを許可した場合、ページはアクセス トークンを使用してサンプル API リクエストを行います。

    API リクエストは、Drive API の about.get メソッドを呼び出して、承認されたユーザーの Google ドライブ アカウントに関する情報を取得します。

  5. リクエストが正常に実行されると、API レスポンスがブラウザのデバッグ コンソールに記録されます。

アプリへのアクセス権は、Google アカウントの [権限] ページで取り消すことができます。アプリは [OAuth 2.0 Demo for Google API Docs] として表示されます。

このコードをローカルで実行するには、認可認証情報に対応する YOUR_CLIENT_ID 変数と YOUR_REDIRECT_URI 変数の値を設定する必要があります。YOUR_REDIRECT_URI 変数は、ページが配信されている URL と同じ URL に設定する必要があります。この値は、 API Console Credentials pageで構成した OAuth 2.0 クライアントの承認済みリダイレクト URI のいずれかと完全に一致する必要があります。この値が承認済みの URI と一致しない場合、redirect_uri_mismatch エラーが発生します。また、プロジェクトでこのリクエストの適切な API を有効にしている必要があります。

<html><head></head><body>
<script>
  var YOUR_CLIENT_ID = 'REPLACE_THIS_VALUE';
  var YOUR_REDIRECT_URI = 'REPLACE_THIS_VALUE';

  // Parse query string to see if page request is coming from OAuth 2.0 server.
  var fragmentString = location.hash.substring(1);
  var params = {};
  var regex = /([^&=]+)=([^&]*)/g, m;
  while (m = regex.exec(fragmentString)) {
    params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
  }
  if (Object.keys(params).length > 0 && params['state']) {
    if (params['state'] == localStorage.getItem('state')) {
      localStorage.setItem('oauth2-test-params', JSON.stringify(params) );

      trySampleRequest();
    } else {
      console.log('State mismatch. Possible CSRF attack');
    }
  }

  // Function to generate a random state value
  function generateCryptoRandomState() {
    const randomValues = new Uint32Array(2);
    window.crypto.getRandomValues(randomValues);

    // Encode as UTF-8
    const utf8Encoder = new TextEncoder();
    const utf8Array = utf8Encoder.encode(
      String.fromCharCode.apply(null, randomValues)
    );

    // Base64 encode the UTF-8 data
    return btoa(String.fromCharCode.apply(null, utf8Array))
      .replace(/\+/g, '-')
      .replace(/\//g, '_')
      .replace(/=+$/, '');
  }

  // If there's an access token, try an API request.
  // Otherwise, start OAuth 2.0 flow.
  function trySampleRequest() {
    var params = JSON.parse(localStorage.getItem('oauth2-test-params'));
    if (params && params['access_token']) { 
      // User authorized the request. Now, check which scopes were granted.
      if (params['scope'].includes('https://www.googleapis.com/auth/drive.metadata.readonly')) {
        // User authorized read-only Drive activity permission.
        // Calling the APIs, etc.
        var xhr = new XMLHttpRequest();
        xhr.open('GET',
          'https://www.googleapis.com/drive/v3/about?fields=user&' +
          'access_token=' + params['access_token']);
        xhr.onreadystatechange = function (e) {
          if (xhr.readyState === 4 && xhr.status === 200) {
            console.log(xhr.response);
          } else if (xhr.readyState === 4 && xhr.status === 401) {
            // Token invalid, so prompt for user permission.
            oauth2SignIn();
          }
        };
        xhr.send(null);
      }
      else {
        // User didn't authorize read-only Drive activity permission.
        // Update UX and application accordingly
        console.log('User did not authorize read-only Drive activity permission.');
      }

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

  /*
   * Create form to request access token from Google's OAuth 2.0 server.
   */
  function oauth2SignIn() {
    // create random state value and store in local storage
    var state = generateCryptoRandomState();
    localStorage.setItem('state', state);

    // Google's OAuth 2.0 endpoint for requesting an access token
    var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';

    // Create element to open OAuth 2.0 endpoint in new window.
    var form = document.createElement('form');
    form.setAttribute('method', 'GET'); // Send as a GET request.
    form.setAttribute('action', oauth2Endpoint);

    // Parameters to pass to OAuth 2.0 endpoint.
    var params = {'client_id': YOUR_CLIENT_ID,
                  'redirect_uri': YOUR_REDIRECT_URI,
                  'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly',
                  'state': state,
                  'include_granted_scopes': 'true',
                  'response_type': 'token'};

    // Add form parameters as hidden input values.
    for (var p in params) {
      var input = document.createElement('input');
      input.setAttribute('type', 'hidden');
      input.setAttribute('name', p);
      input.setAttribute('value', params[p]);
      form.appendChild(input);
    }

    // Add form to page and submit it to open the OAuth 2.0 endpoint.
    document.body.appendChild(form);
    form.submit();
  }
</script>

<button onclick="trySampleRequest();">Try sample request</button>
</body></html>

JavaScript 生成元検証ルール

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

検証規則
スキーム

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

ホスト

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

ドメイン
  • ホストの TLD(トップレベル ドメイン)は、公開サフィックス リストに属している必要があります。
  • ホストドメインは “googleusercontent.com” にできません。
  • JavaScript のオリジンに URL 短縮ドメイン(goo.gl など)を含めることはできません(アプリがドメインを所有している場合を除く)。
  • Userinfo

    JavaScript オリジンに userinfo サブコンポーネントを含めることはできません。

    [Path]

    JavaScript のオリジンにパス コンポーネントを含めることはできません。

    クエリ

    JavaScript オリジンにクエリ コンポーネントを含めることはできません。

    Fragment

    JavaScript オリジンにフラグメント コンポーネントを含めることはできません。

    文字数 JavaScript 生成元には、次のような特定の文字を含めることはできません。
    • ワイルドカード文字('*'
    • 印刷不可能な ASCII 文字
    • 無効なパーセント エンコード(パーセント記号の後に 2 桁の 16 進数を続ける URL エンコード形式に従わないパーセント エンコード)
    • null 文字(エンコードされた NULL 文字、%00%C0%80

    段階的な認可

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

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

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

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

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

    以下のコードサンプルは、既存のアクセス トークンにスコープを追加する方法を示しています。このアプローチにより、アプリで複数のアクセス トークンを管理する必要がなくなります。

    OAuth 2.0 エンドポイント

    既存のアクセス トークンにスコープを追加するには、Google の OAuth 2.0 サーバーに送信するリクエストinclude_granted_scopes パラメータを追加します。

    次のコード スニペットは、その方法を示しています。このスニペットは、アクセス トークンが有効なスコープをブラウザのローカル ストレージに保存していることを前提としています。(完全な例のコードでは、ブラウザのローカル ストレージに oauth2-test-params.scope プロパティを設定して、アクセス トークンが有効なスコープのリストを保存します)。

    このスニペットは、アクセス トークンが有効なスコープと、特定のクエリに使用するスコープを比較します。アクセス トークンがそのスコープをカバーしていない場合、OAuth 2.0 フローが開始されます。ここで、oauth2SignIn 関数はステップ 2 で提供された関数と同じです(後述の完全な例で提供されます)。

    var SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly';
    var params = JSON.parse(localStorage.getItem('oauth2-test-params'));
    
    var current_scope_granted = false;
    if (params.hasOwnProperty('scope')) {
      var scopes = params['scope'].split(' ');
      for (var s = 0; s < scopes.length; s++) {
        if (SCOPE == scopes[s]) {
          current_scope_granted = true;
        }
      }
    }
    
    if (!current_scope_granted) {
      oauth2SignIn(); // This function is defined elsewhere in this document.
    } else {
      // Since you already have access, you can proceed with the API request.
    }

    トークンの取り消し

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

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

    OAuth 2.0 エンドポイント

    プログラムでトークンを取り消すには、アプリが 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 が返されます。

    次の JavaScript スニペットは、Google APIs JavaScript クライアント ライブラリを使用せずに JavaScript でトークンを取り消す方法を示しています。トークンの取り消しを行う Google OAuth 2.0 エンドポイントはクロスオリジン リソース シェアリング(CORS)をサポートしていないため、コードは XMLHttpRequest() メソッドを使用してリクエストを POST するのではなく、フォームを作成してエンドポイントに送信します。

    function revokeAccess(accessToken) {
      // Google's OAuth 2.0 endpoint for revoking access tokens.
      var revokeTokenEndpoint = 'https://oauth2.googleapis.com/revoke';
    
      // Create <form> element to use to POST data to the OAuth 2.0 endpoint.
      var form = document.createElement('form');
      form.setAttribute('method', 'post');
      form.setAttribute('action', revokeTokenEndpoint);
    
      // Add access token to the form so it is set as value of 'token' parameter.
      // This corresponds to the sample curl request, where the URL is:
      //      https://oauth2.googleapis.com/revoke?token={token}
      var tokenField = document.createElement('input');
      tokenField.setAttribute('type', 'hidden');
      tokenField.setAttribute('name', 'token');
      tokenField.setAttribute('value', accessToken);
      form.appendChild(tokenField);
    
      // Add form to page and submit it to actually revoke the token.
      document.body.appendChild(form);
      form.submit();
    }

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

    ユーザーのアカウントを保護するために、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

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