用戶端網路應用程式的 OAuth 2.0

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

本文說明如何實作 OAuth 2.0 授權,從 JavaScript 網路應用程式存取 Google API。OAuth 2.0 可讓使用者與應用程式共用特定資料,同時保有使用者名稱、密碼和其他資訊的隱私性。 舉例來說,應用程式可以使用 OAuth 2.0,取得使用者權限,以便將檔案儲存在 Google 雲端硬碟中。

這個 OAuth 2.0 流程稱為隱含授權流程。本應用程式專為在應用程式於使用者存取時才存取 API 的應用程式設計。這些應用程式無法儲存機密資訊。

在這個流程中,系統會開啟一個 Google 網址,使用查詢參數來識別您的應用程式,以及應用程式需要的 API 存取權類型。您可以在目前的瀏覽器視窗或彈出式視窗中開啟網址。使用者可以透過 Google 進行驗證並授予要求的權限。接著,Google 會將使用者重新導向回您的應用程式。重新導向中包含了存取憑證,應用程式會驗證該權杖並用來提出 API 要求。

必要條件

為您的專案啟用 API

所有呼叫 Google API 的應用程式都必須在 API Console中啟用這些 API。

為專案啟用 API:

  1. Open the API Library 在 Google API Console內。
  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 credentials] (建立憑證) > [OAuth client ID] (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:設定用戶端物件

如果您使用 JavaScript 專用 Google API 用戶端程式庫來處理 OAuth 2.0 流程,請先設定 gapi.auth2gapi.client 物件。這些物件可讓應用程式取得使用者授權及提出授權的 API 要求。

用戶端物件會識別應用程式要求存取範圍的範圍。這些值可讓 Google 向使用者顯示同意畫面。

JS 用戶端程式庫

JavaScript 用戶端程式庫可簡化授權程序的多個層面:

  1. 它會為 Google 的授權伺服器建立重新導向網址,並提供將使用者導向該網址的方法。
  2. 該伺服器會處理該伺服器重新導向到您的應用程式。
  3. 該伺服器會驗證授權伺服器傳回的存取憑證。
  4. 會儲存授權伺服器傳送給應用程式的存取憑證,並在應用程式日後發出授權的 API 呼叫時擷取這個存取憑證。

以下程式碼片段是本文件後半部完整範例的摘錄。這個程式碼會初始化 gapi.client 物件,物件之後會用來進行 API 呼叫。建立此物件時,也會初始化 gapi.auth2 物件 (您的應用程式用來檢查及監控使用者的授權狀態)。

呼叫 gapi.client.init 可指定下列欄位:

  • apiKeyclientId 值會指定應用程式的授權憑證。如建立授權憑證一節所述,您可以在 API Console中取得這些值。請注意,如果應用程式發出已授權的 API 要求,就必須提供 clientId。僅提出未經授權要求的應用程式只能指定 API 金鑰。
  • scope 欄位會指定以空格分隔的存取權範圍清單,此清單會對應至您的應用程式可在使用者代表使用者存取的資源中。這些值可以讓 Google 向使用者顯示同意畫面。

    建議您盡可能讓應用程式在適用情況下要求存取授權範圍。藉由在背景要求,逐步要求取得使用者資料的存取權,有助於使用者更輕鬆地瞭解應用程式需要其所要求存取權的原因。

  • discoveryDocs 欄位可用來識別應用程式使用的 API 探索文件清單。Discovery 文件介紹了 API 的介面 (包括資源結構定義),而 JavaScript 用戶端程式庫會使用這些資訊產生應用程式可使用的方法。在本範例中,程式碼會擷取 Google Drive API 第 3 版的探索文件。

gapi.client.init 呼叫完成後,程式碼會設定 GoogleAuth 變數來識別 Google 驗證物件。最後,程式碼會設定事件監聽器,在使用者登入狀態改變時呼叫函式。(程式碼片段中並未定義該函式)。

var GoogleAuth; // Google Auth object.
function initClient() {
  gapi.client.init({
      'apiKey': 'YOUR_API_KEY',
      'clientId': 'YOUR_CLIENT_ID',
      'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly',
      'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']
  }).then(function () {
      GoogleAuth = gapi.auth2.getAuthInstance();

      // Listen for sign-in state changes.
      GoogleAuth.isSignedIn.listen(updateSigninStatus);
  });
}

OAuth 2.0 端點

如果您是直接存取 OAuth 2.0 端點,可進行下一個步驟。

步驟 2:重新導向至 Google 的 OAuth 2.0 伺服器

如果想要求存取使用者的資料,請將使用者重新導向至 Google 的 OAuth 2.0 伺服器。

JS 用戶端程式庫

呼叫 GoogleAuth.signIn() 方法,將使用者導向 Google 的授權伺服器。

GoogleAuth.signIn();

在實務上,您的應用程式可能會設定布林值來判定在呼叫 API 呼叫之前,是否要呼叫 signIn() 方法。

下方的程式碼片段示範如何執行使用者授權流程。請注意下列程式碼片段的相關要點:

  • 程式碼中參照的 GoogleAuth 物件與步驟 1 的程式碼片段中定義的全域變數相同。

  • updateSigninStatus 函式是一個監聽器,可監聽使用者的授權狀態變更。步驟 1 的程式碼片段中也定義了事件監聽器的角色:
    GoogleAuth.isSignedIn.listen(updateSigninStatus);
  • 這段程式碼會定義兩個額外的全域變數:

    • isAuthorized 是一個布林值變數,用於指出使用者是否已登入。這個值可在應用程式載入時設定,並在使用者登入或登出應用程式時更新。

      在這個程式碼片段中,sendAuthorizedApiRequest 函式會檢查變數值,藉此判斷應用程式是否應嘗試需要授權的 API 要求,或是提示使用者授權應用程式。

    • currentApiRequest 是一個物件,用於儲存使用者嘗試最後一次 API 要求的詳細資料。應用程式呼叫 sendAuthorizedApiRequest 函式時,會設定物件值。

      如果使用者已授權應用程式,系統會立即執行要求。否則,函式會將使用者重新導向。使用者登入後,updateSignInStatus 函式會呼叫 sendAuthorizedApiRequest,並傳入在授權流程開始之前嘗試執行的要求。

var isAuthorized;
var currentApiRequest;

/**
 * Store the request details. Then check to determine whether the user
 * has authorized the application.
 *   - If the user has granted access, make the API request.
 *   - If the user has not granted access, initiate the sign-in flow.
 */
function sendAuthorizedApiRequest(requestDetails) {
  currentApiRequest = requestDetails;
  if (isAuthorized) {
    // Make API request
    // gapi.client.request(requestDetails)

    // Reset currentApiRequest variable.
    currentApiRequest = {};
  } else {
    GoogleAuth.signIn();
  }
}

/**
 * Listener called when user completes auth flow. If the currentApiRequest
 * variable is set, then the user was prompted to authorize the application
 * before the request executed. In that case, proceed with that API request.
 */
function updateSigninStatus(isSignedIn) {
  if (isSignedIn) {
    isAuthorized = true;
    if (currentApiRequest) {
      sendAuthorizedApiRequest(currentApiRequest);
    }
  } else {
    isAuthorized = false;
  }
}

OAuth 2.0 端點

產生網址以向 Google 的 OAuth 2.0 端點要求存取權 (位於 https://accounts.google.com/o/oauth2/v2/auth)。這個端點可透過 HTTPS 存取,系統會拒絕一般的 HTTP 連線。

Google 授權伺服器支援下列網路應用程式應用程式的查詢字串參數:

參數總數
client_id 必要

應用程式的用戶端 ID。您可以在 API ConsoleCredentials page中找到這個值。

redirect_uri 必要

決定 API 伺服器在使用者完成授權流程後將使用者重新導向到哪個位置。這個值必須與您在用戶端的 API ConsoleCredentials page中設定的 OAuth 2.0 用戶端授權重新導向 URI 完全相符。如果這個值與提供的 client_id 的授權重新導向 URI 不符,您就會收到 redirect_uri_mismatch 錯誤。

請注意,httphttps 配置、大小寫和結尾的斜線 ('/') 必須一致。

response_type 必要

JavaScript 應用程式必須將參數值設為 token。這個值會指示 Google 授權伺服器將存取憑證以 name=value 組合的形式傳回,並用於 URI 的片段 ID (#)。

scope 必要

以空格分隔的範圍清單,用於識別應用程式代表使用者可以存取的資源。這些值可以讓 Google 向使用者顯示同意畫面。

範圍可讓應用程式只要求存取所需資源的存取權,同時也能控管使用者授予應用程式存取權的權限。因此,要求的範圍數量與取得使用者同意的可能性之間存在逆向關係。

建議您就應用程式情況,在可行的情況下要求存取授權範圍。透過在漸進式授權的情況下,藉由在要求中存取使用者資料,可以讓使用者更容易瞭解應用程式需要其所要求存取權的原因。

state 建議使用

指定您的應用程式用來在授權要求與授權伺服器回應之間的狀態時,所使用的任何字串值。在使用者同意或拒絕應用程式存取要求後,伺服器會傳回您在 redirect_uri 網址片段 ID (#) 中做為 name=value 組合傳送的確切值。

此參數有許多用途,例如將使用者導向應用程式中的正確資源、傳送 nonce 和緩解跨網站偽造要求。由於系統可猜測您的 redirect_uri,因此提高 state 值之後,就能確保傳入的連線是驗證要求產生的結果。如果您產生隨機字串,或對 Cookie 或其他會擷取用戶端狀態的值進行編碼,就可以驗證回應,進一步確保要求和回應源自同一個瀏覽器,藉此防範跨網站偽造要求等攻擊。請參閱 OpenID Connect 說明文件,瞭解如何建立及確認 state 憑證的範例。

include_granted_scopes 選用

允許應用程式使用漸進式授權,要求對內容的其他範圍進行存取。如果您將這個參數的值設為 true,且已獲授權要求,新的存取權杖也會涵蓋使用者先前授予應用程式存取權的任何範圍。相關範例請參閱增量授權一節。

login_hint 選用

如果您的應用程式知道要嘗試驗證的使用者,可使用這個參數向 Google 驗證伺服器提供提示。伺服器會使用提示在登入表單中預先填入電子郵件欄位,或選取適當的多帳戶登入工作階段,藉此簡化登入流程。

將參數值設為電子郵件地址或 sub 識別碼,等同於使用者的 Google ID。

prompt 選用

以空格分隔且區分大小寫的提示清單,可向使用者顯示。如未指定這個參數,系統只會在第一次要求專案存取權時提示使用者。詳情請參閱提示重新同意一文。

可能的值為:

none 請勿顯示任何驗證或同意畫面。不得指定其他值。
consent 提示使用者提供同意聲明。
select_account 提示使用者選取帳戶。

重新導向到 Google 的授權伺服器範例

下方範例網址顯示如下,當中有換行符號和空格可讀。

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.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

建立要求網址後,將使用者重新導向到該網址。

JavaScript 範例程式碼

下列 JavaScript 程式碼片段顯示如何在不使用 JavaScript 的 Google API 用戶端程式庫時,以 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',
                '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();
}

步驟 3:Google 提示使用者提供同意聲明

在這個步驟中,使用者自行決定是否授予應用程式要求的存取權。在這個階段,Google 會顯示同意視窗,其中顯示您的應用程式名稱以及要求取得使用者授權憑證存取權的 Google API 服務,以及授予的存取權範圍摘要。之後,使用者可以同意授予應用程式一或多個範圍的存取權,或拒絕要求。

您的應用程式在等待 Google 的 OAuth 2.0 伺服器回應指出已授予任何存取權時,目前這個階段不需要採取任何行動。我們會在下文中說明回應。

錯誤

向 Google 的 OAuth 2.0 授權端點發出的要求可能會顯示使用者端的錯誤訊息,而不是預期的驗證和授權流程。以下列出常見的錯誤代碼和建議解決方法。

admin_policy_enforced

根據 Google Workspace 管理員的政策,Google 帳戶無法授權一或多個要求的範圍。請參閱 Google Workspace 管理員說明文章「控管哪些第三方與內部應用程式可存取 Google Workspace 資料」一文,在管理員明確授權您的 OAuth 用戶端 ID 之前,限制管理員如何限制對所有範圍或機密和受限制範圍的存取權。

disallowed_useragent

授權端點會顯示在嵌入的使用者代理程式內,且不受 Google 的 OAuth 2.0 政策規定。

Android

Android 開發人員在 android.webkit.WebView 中開啟授權要求時,可能會看到這則錯誤訊息。開發人員應改用 Android 程式庫,例如 Android 適用的 Google 登入或 OpenID Foundation (Android 專用的 AppAuth)。

如果 Android 應用程式會在嵌入的使用者代理程式中開啟一般網頁連結,而且使用者前往您的網站的 Google 的 OAuth 2.0 授權端點,網站開發人員就可能遇到這個錯誤。開發人員應允許在一般預設連結處理常式中,透過 Android App Links 處理常式或預設的瀏覽器應用程式開啟一般連結。Android 自訂分頁程式庫也是一個支援的選項。

iOS

iOS 和 macOS 開發人員在 WKWebView 中開啟授權要求時,可能會遇到這個錯誤。開發人員應改用 iOS 程式庫,例如 Google Sign-In for iOS 或 OpenID Foundation (#39;s) AppAuth for iOS

當 iOS 或 macOS 應用程式在嵌入式使用者代理程式中開啟一般網頁連結,而且使用者前往您的網站的 Google 的 OAuth 2.0 授權端點時,網頁開發人員可能會遇到這個錯誤。開發人員應允許在一般預設連結處理常式中開啟作業系統的連結,該連結含有通用連結處理常式或預設瀏覽器應用程式。此外,系統也支援 SFSafariViewController 程式庫。

org_internal

要求中的 OAuth 用戶端 ID 屬於一項專案,會限制對特定 Google Cloud 機構中存取 Google 帳戶的專案。如要進一步瞭解這個設定選項,請參閱「設定 OAuth 同意畫面」說明文章中的「使用者類型」一節。

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 來源。

步驟 4:處理 OAuth 2.0 伺服器回應

JS 用戶端程式庫

JavaScript 用戶端程式庫會處理 Google 的授權伺服器的回應。如果您將事件監聽器設定為監控目前使用者登入狀態的變更,系統會在使用者授予應用程式要求的存取權時呼叫該函式。

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 參數以外,片段字串還包含 token_type 參數 (一律設為 Bearer),以及指定憑證生命週期的 expires_in 參數 (以秒為單位)。如果存取憑證要求中指定了 state 參數,其值也會包含在回應中。

  • 錯誤回應:
    https://oauth2.example.com/callback#error=access_denied

OAuth 2.0 伺服器回應範例

您可以按一下下列網址範例來測試此流程,要求取得唯讀權限以查看 Google 雲端硬碟檔案的中繼資料:

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.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。除非您的本機電腦確實使用該位址提供檔案,否則該網址會產生 404 NOT FOUND 錯誤。下一步是提供更多詳細資訊,說明使用者被重新導向到應用程式時,在 URI 中傳回的資訊。

呼叫 Google API

JS 用戶端程式庫

應用程式取得存取憑證後,您就能使用 JavaScript 用戶端程式庫代表使用者發出 API 要求。用戶端程式庫可為您管理存取憑證,您不需要在要求中傳送任何特殊的請求。

用戶端程式庫支援呼叫 API 方法的兩種方法。如果您已載入探索文件,API 會為您定義方法專屬函式。您也可以使用 gapi.client.request 函式呼叫 API 方法。下列兩段程式碼展示了 Drive API 的 about.get 方法。

// Example 1: Use method-specific function
var request = gapi.client.drive.about.get({'fields': 'user'});

// Execute the API request.
request.execute(function(response) {
  console.log(response);
});


// Example 2: Use gapi.client.request(args) function
var request = gapi.client.request({
  'method': 'GET',
  'path': '/drive/v3/about',
  'params': {'fields': 'user'}
});
// Execute the API request.
request.execute(function(response) {
  console.log(response);
});

OAuth 2.0 端點

在應用程式取得存取憑證後,如果系統授予了 API 所需的存取權範圍,您就可以使用這個符記代表特定使用者帳戶呼叫 Google API。方法是加入 API 要求,加入 access_token 查詢參數或 Authorization HTTP 標頭 Bearer 值。可以的話,建議使用 HTTP 標頭,因為查詢字串通常會顯示在伺服器記錄中。在大多數情況下,您都可以使用用戶端程式庫來設定對 Google API 的呼叫 (例如在呼叫 Drive Files API 時)。

您可以試用所有 Google API,並在 OAuth 2.0 Playground 中查看其範圍。

HTTP GET 範例

使用 Authorization: Bearer HTTP 標頭呼叫 drive.files 端點 (Drive API 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);

完整範例

JS 用戶端程式庫

程式碼範例範例

本節包含程式碼範例的示範影片,其中示範程式碼在實際應用程式中的行為。授權應用程式後,就會在與您 Google 帳戶連結的應用程式中列出該應用程式。這個應用程式的名稱為 OAuth 2.0 示範 Google API 文件。同樣地,如果您撤銷存取權並重新整理該網頁,就不會再列出該應用程式。

請注意,這個應用程式要求存取 https://www.googleapis.com/auth/drive.metadata.readonly 範圍。系統僅會提出存取要求,以示範如何在 JavaScript 應用程式中啟動 OAuth 2.0 流程。這個應用程式沒有任何 API 要求。

JavaScript 範例程式碼

如上所示,此程式碼範例適用於會載入 JavaScript API 專用 Google API 用戶端程式庫,並執行 OAuth 2.0 流程的網頁 (應用程式)。頁面上會顯示:

  • 可讓使用者登入應用程式的一個按鈕。如果使用者先前並未授權應用程式,應用程式會啟動 OAuth 2.0 流程。
  • 使用者可以透過兩個按鈕登出應用程式,或是撤銷先前授予應用程式的存取權。如果登出應用程式,你尚未撤銷應用程式的存取權。

您也可以透過 Google 帳戶的權限頁面撤銷應用程式的存取權。應用程式已列為 Google API 專用 OAuth 2.0 示範

<script>
  var GoogleAuth;
  var SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly';
  function handleClientLoad() {
    // Load the API's client and auth2 modules.
    // Call the initClient function after the modules load.
    gapi.load('client:auth2', initClient);
  }

  function initClient() {
    // In practice, your app can retrieve one or more discovery documents.
    var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';

    // Initialize the gapi.client object, which app uses to make API requests.
    // Get API key and client ID from API Console.
    // 'scope' field specifies space-delimited list of access scopes.
    gapi.client.init({
        'apiKey': 'YOUR_API_KEY',
        'clientId': 'YOUR_CLIENT_ID',
        'discoveryDocs': [discoveryUrl],
        'scope': SCOPE
    }).then(function () {
      GoogleAuth = gapi.auth2.getAuthInstance();

      // Listen for sign-in state changes.
      GoogleAuth.isSignedIn.listen(updateSigninStatus);

      // Handle initial sign-in state. (Determine if user is already signed in.)
      var user = GoogleAuth.currentUser.get();
      setSigninStatus();

      // Call handleAuthClick function when user clicks on
      //      "Sign In/Authorize" button.
      $('#sign-in-or-out-button').click(function() {
        handleAuthClick();
      });
      $('#revoke-access-button').click(function() {
        revokeAccess();
      });
    });
  }

  function handleAuthClick() {
    if (GoogleAuth.isSignedIn.get()) {
      // User is authorized and has clicked "Sign out" button.
      GoogleAuth.signOut();
    } else {
      // User is not signed in. Start Google auth flow.
      GoogleAuth.signIn();
    }
  }

  function revokeAccess() {
    GoogleAuth.disconnect();
  }

  function setSigninStatus() {
    var user = GoogleAuth.currentUser.get();
    var isAuthorized = user.hasGrantedScopes(SCOPE);
    if (isAuthorized) {
      $('#sign-in-or-out-button').html('Sign out');
      $('#revoke-access-button').css('display', 'inline-block');
      $('#auth-status').html('You are currently signed in and have granted ' +
          'access to this app.');
    } else {
      $('#sign-in-or-out-button').html('Sign In/Authorize');
      $('#revoke-access-button').css('display', 'none');
      $('#auth-status').html('You have not authorized this app or you are ' +
          'signed out.');
    }
  }

  function updateSigninStatus() {
    setSigninStatus();
  }
</script>

<button id="sign-in-or-out-button"
        style="margin-left: 25px">Sign In/Authorize</button>
<button id="revoke-access-button"
        style="display: none; margin-left: 25px">Revoke access</button>

<div id="auth-status" style="display: inline; padding-left: 25px"></div><hr>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
        onload="this.onload=function(){};handleClientLoad()"
        onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>

OAuth 2.0 端點

這個程式碼範例示範如何在不使用 JavaScript 的 Google API 用戶端程式庫的情況下,以 JavaScript 完成 OAuth 2.0 流程。程式碼是 HTML 網頁,其中的按鈕可用來試用 API 要求。只要按一下按鈕,程式碼就會檢查網頁是否已將 API 存取憑證儲存在瀏覽器的本機儲存空間中。如果是的話,將執行 API 要求。否則,系統會啟動 OAuth 2.0 流程。

如果是 OAuth 2.0 流程,此網頁可按照下列步驟進行:

  1. 系統會將使用者導向 Google 的 OAuth 2.0 伺服器,該伺服器會要求存取 https://www.googleapis.com/auth/drive.metadata.readonly 範圍。
  2. 授予 (或拒絕) 對一或多個要求範圍的存取權後,系統會將使用者重新導向至原始頁面,從片段 ID 字串剖析存取權杖。
  3. 這個頁面使用存取憑證來提出範例 API 要求。

    API 要求會呼叫 Drive API 的 about.get 方法,以擷取授權使用者的 Google 雲端硬碟帳戶相關資訊。

  4. 如果要求成功執行,API 回應就會記錄在瀏覽器的偵錯主控台中。

您可以透過 Google 帳戶的權限頁面撤銷應用程式的存取權。應用程式會列為 Google API 專用 OAuth 2.0 示範

如要在本機執行此程式碼,您必須針對與您的授權憑證相對應的 YOUR_CLIENT_IDYOUR_REDIRECT_URI 變數設定值。YOUR_REDIRECT_URI 變數應設為提供該網頁的相同網址。這個值必須與您在 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';
  var fragmentString = location.hash.substring(1);

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

  // 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']) {
      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 {
      oauth2SignIn();
    }
  }

  /*
   * Create form to request access token from Google's OAuth 2.0 server.
   */
  function oauth2SignIn() {
    // 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',
                  'state': 'try_sample_request',
                  '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 來源必須使用 HTTPS 配置,而不是一般 HTTP。本機主機 URI (包括 localhost IP 位址 URI) 則不受此規則影響。

主機

主機不得為原始 IP 位址。本機主機 IP 位址排除在這項規則之外。

網域
  • 主機 TLD (頂層網域) 必須屬於公開尾碼清單
  • 主機網域不得為 “googleusercontent.com”
  • JavaScript 來源不得包含網址縮短網域 (例如 goo.gl),除非應用程式的擁有者是網域的擁有者。
  • 使用者資訊

    JavaScript 來源不得包含 userinfo 子元件。

    路徑

    JavaScript 來源不得包含路徑元件。

    查詢

    JavaScript 來源不得包含查詢元件。

    片段

    JavaScript 來源不得包含片段元件。

    字元 JavaScript 來源不得包含特定字元,包括:
    • 萬用字元字元 ('*')
    • 非列印的 ASCII 字元
    • 百分比編碼無效 (任何未遵循網址編碼格式的百分比百分比,後接 2 個十六進位數字)
    • 空值 (經過編碼的 NULL 字元,例如%00%C0%80)

    遞增授權

    在 OAuth 2.0 通訊協定中,您的應用程式會要求存取資源 (可依範圍識別)。因此,在需要資源授權時,建議您採用最佳使用者體驗。為此,Google 的授權伺服器支援漸進式授權機制。這項功能可讓您按需求要求範圍,如果使用者授予新範圍的權限,則傳回授權碼,可能會交換以作為使用者已授予專案所有範圍的符記。

    舉例來說,允許使用者取得音樂曲目及建立合輯的應用程式,在登入時可能不需要極少資源,因此應該只有登入者的名稱才是資源。然而, 儲存完成的組合需要存取 Google 雲端硬碟。如果大多數人都要求在應用程式實際需要存取 Google 雲端硬碟時要求存取 Google 雲端硬碟,就會覺得資料很自然。

    在這個情況下,應用程式可能會在要求時要求 openidprofile 範圍,以執行基本登入作業,然後再要求在首次要求時產生 https://www.googleapis.com/auth/drive.file 範圍以儲存組合。

    下列規則適用於透過漸進式授權取得的存取憑證:

    • 該憑證可用於存取與新合併授權中涵蓋的任何範圍對應的資源。
    • 當您使用重新整理的權杖取得合併授權以取得存取憑證時,存取憑證代表合併的授權,可用於回應中包含的任何 scope 值。
    • 合併授權包括使用者授予 API 專案的所有範圍,即使這些要求是從不同用戶端提出亦然。舉例來說,如果使用者透過電腦版用戶端授予某個範圍的存取權,然後透過行動用戶端授予另一個範圍的存取權,則合併授權就會同時納入這兩個範圍。
    • 如果您撤銷代表合併授權的權杖,代表關聯使用者的所有授權範圍都會同時遭到撤銷。

    下方的程式碼範例說明瞭如何將範圍新增至現有的存取憑證。如此一來,您的應用程式就不必管理多個存取憑證。

    JS 用戶端程式庫

    如要在現有存取憑證中加入範圍,請呼叫 GoogleUser.grant(options) 方法。options 物件可識別您要授予存取權的其他範圍。

    // Space-separated list of additional scope(s) you are requesting access to.
    // This code adds read-only access to the user's calendars via the Calendar API.
    var NEW_SCOPES = 'https://www.googleapis.com/auth/calendar.readonly';
    
    // Retrieve the GoogleUser object for the current user.
    var GoogleUser = GoogleAuth.currentUser.get();
    GoogleUser.grant({'scope': NEW_SCOPES});

    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 要求,以確保先前授予應用程式的權限已經移除。

    JS 用戶端程式庫

    如要透過程式撤銷憑證,請呼叫 GoogleAuth.disconnect()

    GoogleAuth.disconnect();

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