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

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

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

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

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

JavaScript 用 Google API クライアント ライブラリを使用して Google に承認済み呼び出しを行う場合は、Google Identity Services JavaScript ライブラリを使用して承認フローを処理する必要があります。より安全な PKCE を使用した認可コードフローに基づく Google Identity Services のコードモデルを使用することを強くおすすめします。

前提条件

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

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

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

  1. Google API Console で API ライブラリを開きます
  2. プロンプトが表示されたら、プロジェクトのいずれかを選択するか、新しいプロジェクトを作成します。
  3. API ライブラリには、利用できるすべての API が製品ファミリーと人気度によって分類、表示されます。有効にする API がリストで見当たらない場合は、検索して見つけるか、その API が属するプロダクト ファミリーの [すべて表示] をクリックします。
  4. 有効にする API を選択し、[有効にする] ボタンをクリックします。
  5. プロンプトが表示されたら、請求機能を有効にします。
  6. プロンプトが表示されたら、API の利用規約を確認し、同意します。

承認認証情報を作成する

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

  1. [クライアント] ページに移動します。
  2. [クライアントを作成] をクリックします。
  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 エンドポイント

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

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

パラメータ
client_id 必須

アプリケーションのクライアント ID。この値は、Cloud Console の [クライアント] ページで確認できます。

redirect_uri 必須

ユーザーが認可フローを完了した後に API サーバーがユーザーをリダイレクトする場所を指定します。この値は、クライアントの Google Cloud コンソールのクライアント ページで構成した 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 フラグメント識別子(#)で 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 ユーザーにアカウントの選択を求める。

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

disallowed_useragent

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

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 をご覧ください。

deleted_client

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

invalid_grant

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

origin_mismatch

承認リクエストを送信した JavaScript のスキーム、ドメイン、ポートが、OAuth クライアント ID に登録されている承認済みの JavaScript 生成元 URI と一致していない可能性があります。Google Cloud コンソール の [クライアント] ページで、承認済みの JavaScript 生成元を確認します。

redirect_uri_mismatch

認可リクエストで渡された redirect_uri が、OAuth クライアント ID の承認済みリダイレクト URI と一致しません。Google Cloud コンソールの [クライアント] ページで、承認済みリダイレクト URI を確認します。

承認リクエストを送信した JavaScript のスキーム、ドメイン、ポートが、OAuth クライアント ID に登録されている承認済みの JavaScript 生成元 URI と一致していない可能性があります。Google Cloud コンソール の [クライアント] ページで、承認済みの 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//developers.google.com/oauthplayground&
 client_id=client_id

OAuth 2.0 フローが完了すると、ブラウザは OAuth フローをテストするためのツールである OAuth 2.0 プレイグラウンドにリダイレクトされます。OAuth 2.0 Playground で認証コードが自動的に取得されていることがわかります。

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

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

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

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

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 を呼び出す場合など)。

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

HTTP GET の例

Authorization: Bearer HTTP ヘッダーを使用して drive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.filesdrive.files�独自のアクセス トークンを指定する必要があります。

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 アカウントの [権限] ページで、アプリへのアクセスを取り消すことができます。アプリは、クライアント ID の作成時に OAuth 同意画面内のブランディング ページで指定されたアプリケーション名として表示されます。

このコードをローカルで実行するには、認証情報に対応する YOUR_CLIENT_ID 変数と YOUR_REDIRECT_URI 変数の値を設定する必要があります。YOUR_REDIRECT_URI 変数は、ページが配信されている URL と同じ URL に設定する必要があります。この値は、Cloud コンソールの [クライアント] ページで構成した 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 サブコンポーネントを含めることはできません。

    [パス]

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

    クエリ

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

    Fragment

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

    文字数 JavaScript 生成元には、次の文字を含めることはできません。
    • ワイルドカード文字('*'
    • 印刷不可能な 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 つのスコープへのアクセスを許可し、モバイル クライアントを使用して同じアプリケーションに別のスコープを許可した場合、結合された認可には両方のスコープが含まれます。
    • 結合された認可を表すトークンを取り消すと、関連付けられたユーザーに代わって、その認可のすべてのスコープへのアクセスが同時に取り消されます。

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

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

    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

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