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

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

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

這個 OAuth 2.0 流程稱為隱含授權流程。專為應用程式的使用者存取。這些應用程式無法儲存機密資訊。

在這個流程中,您的應用程式會開啟 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,然後按一下「Enable」按鈕。
  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. 選取 [Web application] 應用程式類型。
  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 探索文件清單。探索文件會說明 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 端點

產生網址,向 https://accounts.google.com/o/oauth2/v2/auth 要求 Google 的 OAuth 2.0 端點存取權。這個端點可透過 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 錯誤。

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

response_type 必要

JavaScript 應用程式必須將參數值設為 token。這個值會指示 Google 授權伺服器在完成授權程序後,將使用者重新導向至 URI 的片段 ID (#) 以 name=value 配對形式傳回存取權杖。

scope 必要

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

範圍可讓應用程式僅要求所需資源的存取權,同時讓使用者控制對應用程式授予的存取權數量。因此,要求的範圍數以及取得使用者同意聲明的可能性之間存在反向關係。

建議您盡可能讓應用程式在相關情境下要求存取授權範圍。藉由使用漸進式授權,在相關情境中要求使用者存取資料,您就能讓使用者更容易瞭解應用程式需要其存取權的原因。

state 建議使用

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

您可以將這個參數用於多種用途,例如將使用者導向應用程式中的正確資源、傳送 Nonce 及減少跨網站偽造要求。由於您可以猜測 redirect_uri,因此使用 state 值可能會提高傳入要求是驗證要求產生的結果。如果您產生隨機字串或對 Cookie 的雜湊或其他擷取用戶端狀態的值,您可以驗證回應,進一步確保要求和回應來自同一個瀏覽器,以提供防範跨網站要求偽造的攻擊。如需如何建立及確認 state 權杖的範例,請參閱 OpenID Connect 說明文件。

include_granted_scopes 選填

可讓應用程式使用漸進式授權,要求在情境中存取其他範圍。如果將這個參數的值設為 true 並獲得授權要求,新的存取權杖也會涵蓋使用者先前授予應用程式存取權的所有範圍。如需範例,請參閱漸進式授權一節。

login_hint 選填

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

將參數值設為電子郵件地址或 sub ID,相當於使用者的 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 中啟動授權流程,而不使用 JavaScript 適用的 Google API 用戶端程式庫。由於這個 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 程式庫,例如 Google 登入 Android 版或 OpenID 基金會的 AppAuth for Android

當 Android 應用程式在嵌入的使用者代理程式中開啟一般網頁連結,而使用者從您的網站前往 Google 的 OAuth 2.0 授權端點時,網頁開發人員可能會遇到這個錯誤。開發人員應允許在一般預設連結處理常式 (包括 Android 應用程式連結處理常式或預設瀏覽器應用程式) 中開啟一般連結。此外,您也可以使用 Android 自訂分頁資料庫。

iOS

iOS 和 macOS 開發人員在 WKWebView 中開啟授權要求時,可能會遇到這個錯誤。開發人員應改用 iOS 程式庫,例如 iOS 適用的 Google 登入或 OpenID Foundation 的 iOS 專用 AppAuth

當 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) 流程。請參閱遷移指南來更新整合作業。

步驟 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 API API 時)。

您可以前往 OAuth 2.0 Playground 試用所有 Google API 並查看其範圍。

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

完整範例

JS 用戶端程式庫

程式碼範例示範

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

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

JavaScript 程式碼範例

如上所示,此程式碼範例適用於會載入 JavaScript 專用 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 流程。這個程式碼適用於顯示顯示 API 要求的按鈕的 HTML 網頁。只要按一下按鈕,程式碼就會檢查網頁是否已將 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 來源不得包含查詢元件。

    Fragment

    JavaScript 來源不能含有片段元件。

    字元 JavaScript 來源不得包含某些字元,包括:
    • 萬用字元 ('*')
    • 不可列印的 ASCII 字元
    • 百分比編碼無效 (百分比符號的編碼百分比未遵循百分比符號,後面加上兩個十六進位數字)
    • 空值字元 (經過編碼的 NULL 字元,例如%00%C0%80)

    漸進式授權

    在 OAuth 2.0 通訊協定中,您的應用程式會要求存取資源,這類資源以範圍識別。在需要時為資源要求授權時,這是一種最佳使用者體驗。為了執行這種做法,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 中撤銷憑證,而不使用 JavaScript 適用的 Google API 用戶端程式庫。由於撤銷憑證的 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();
    }