遷移至 Google Identity 服務

總覽

如要取得用於呼叫 Google API 的個別使用者存取權杖,Google 提供多個 JavaScript 程式庫:

本指南提供從這些程式庫遷移至 Google Identity 服務程式庫的操作說明。

按照本指南操作,您將可:

  • 將已淘汰的平台資料庫替換為 Identity 服務資料庫,
  • 如果您使用 API 用戶端程式庫,請移除已淘汰的 gapi.auth2 模組、其方法和物件,並以 Identity Services 對等項目取代。

如需 Identity Services JavaScript 程式庫異動的相關說明,請參閱總覽使用者授權的運作方式,詳閱重要詞彙和概念。

如果您想瞭解使用者註冊和登入的驗證機制,請改為參閱「從 Google 登入移轉」一文。

找出授權流程

使用者授權流程有兩種可能:隱含和授權碼。

查看您的網頁應用程式,找出目前使用的授權流程類型。

以下是網路應用程式使用隱含流程的徵兆:

  • 您的網頁應用程式完全以瀏覽器為基礎,沒有後端平台。
  • 使用者必須在場才能呼叫 Google API,您的應用程式只會使用存取權杖,不需要重新整理權杖。
  • 網頁應用程式會載入 apis.google.com/js/api.js
  • 您的實作方式是以 用戶端網頁應用程式的 OAuth 2.0 為基礎。
  • 您的應用程式會使用 JavaScript 適用的 Google API 用戶端程式庫中的 gapi.clientgapi.auth2 模組。

網頁應用程式使用授權碼流程的徵兆:

在某些情況下,您的程式碼集可能會支援這兩種流程。

選擇授權流程

在開始遷移之前,您必須確定是要繼續現有的流程,還是採用其他流程最符合您的需求。

請參閱選擇授權流程,瞭解兩種流程之間的主要差異和取捨。

在大多數情況下,建議您使用授權碼流程,因為這種流程能提供最高等級的使用者安全性。實作這個流程後,平台就能更輕鬆地新增離線功能,例如擷取更新內容,以便通知使用者日曆、相片、訂閱項目等的重大變更。

使用下方的選取器選擇授權流程。

隱含流程

在使用者在場時取得存取權杖,以便在瀏覽器中使用。

隱含流程範例:顯示遷移至 Identity 服務前後的網頁應用程式。

授權碼流程

Google 核發的每個使用者授權碼會傳送至您的後端平台,然後交換為存取權杖和更新權杖。

授權碼流程範例:顯示遷移至身分識別服務前後的網頁應用程式。

在本指南中,請按照粗體字列出的操作說明,新增移除更新取代現有功能。

瀏覽器內網頁應用程式異動

本節將說明在遷移至 Google Identity 服務 JavaScript 程式庫時,您需要對瀏覽器內網頁應用程式做出哪些變更。

找出受影響的程式碼並進行測試

偵錯 Cookie 可協助您找出受影響的程式碼,並測試淘汰後的行為。

在大型或複雜的應用程式中,您可能很難找出所有受 gapi.auth2 模組淘汰影響的程式碼。如要向主控台記錄即將淘汰的功能目前使用情形,請將 G_AUTH2_MIGRATION Cookie 的值設為 informational。或者,您也可以新增半形冒號,後面加上一個鍵值,一併記錄到工作階段儲存空間。在您登入並收到憑證後,系統會檢查憑證,或將收集到的記錄檔傳送至後端,以利日後分析。例如,informational:showauth2use 會將來源和網址儲存到名為 showauth2use 的工作階段儲存空間金鑰。

如要驗證 gapi.auth2 模組不再載入時的應用程式行為,請將 G_AUTH2_MIGRATION Cookie 的值設為 enforced。這樣一來,您就能在淘汰日期前測試淘汰後的行為。

可能的 G_AUTH2_MIGRATION Cookie 值:

  • enforced 請勿載入 gapi.auth2 模組。
  • informational 將已淘汰的功能使用情形記錄到 JS 控制台。如果設定選用的鍵名,也記錄到工作階段儲存空間:informational:key-name

為盡量減少使用者受到的影響,建議您先在開發和測試期間在本機設定這個 Cookie,然後再於實際工作環境中使用。

程式庫與模組

gapi.auth2 模組會管理登入使用者的驗證作業和隱含授權流程,將這個已淘汰的模組及其物件和方法替換為 Google Identity 服務程式庫。

新增 Identity Services 程式庫至您的網頁應用程式,方法是將該程式庫納入文件中:

<script src="https://accounts.google.com/gsi/client" async defer></script>

使用 gapi.load('auth2', function) 移除任何載入 auth2 模組的例項。

Google Identity 服務資料庫會取代 gapi.auth2 模組的使用方式。您可以繼續安全地使用 適用於 JavaScript 的 Google API 用戶端程式庫中的 gapi.client 模組,並利用其自動建立可呼叫的 JS 方法、批次處理多個 API 呼叫,以及 CORS 管理功能的優勢。

Cookie

使用者授權不需要使用 Cookie。

如要進一步瞭解使用者驗證如何使用 Cookie,請參閱「從 Google 登入遷移」一文,以及「Google 如何使用 Cookie」一文,瞭解其他 Google 產品和服務如何使用 Cookie。

憑證

Google Identity Services 會將使用者驗證和授權分為兩個不同的作業,並將使用者憑證分開:用於識別使用者的 ID 權杖會與用於授權的存取權杖分開傳回。

如要查看這些變更,請參閱範例憑證

隱含流程

從授權流程中移除使用者設定檔處理,以便分開使用者驗證和授權。

移除下列 Google 登入 JavaScript 用戶端參照

方法

  • GoogleUser.getBasicProfile()
  • GoogleUser.getId()

授權碼流程

Identity Services 會將瀏覽器內憑證分為 ID 權杖和存取權杖。這項異動不適用於透過後端平台直接呼叫 Google OAuth 2.0 端點,或透過在平台上安全伺服器上執行的程式庫 (例如 Google API Node.js 用戶端) 取得的憑證。

工作階段狀態

先前,Google 登入可協助您使用以下方式管理使用者的登入狀態:

您必須負責管理網頁應用程式的登入狀態和使用者工作階段。

移除下列 Google 登入 JavaScript 用戶端參考資料

物件:

  • gapi.auth2.SignInOptions

方法:

  • GoogleAuth.attachClickHandler()
  • GoogleAuth.isSignedIn()
  • GoogleAuth.isSignedIn.get()
  • GoogleAuth.isSignedIn.listen()
  • GoogleAuth.signIn()
  • GoogleAuth.signOut()
  • GoogleAuth.currentUser.get()
  • GoogleAuth.currentUser.listen()
  • GoogleUser.isSignedIn()

用戶端設定

更新網路應用程式,為隱含或授權碼流程初始化權杖用戶端。

移除下列 Google 登入 JavaScript 用戶端參照

物件:

  • gapi.auth2.ClientConfig
  • gapi.auth2.OfflineAccessOptions

方法:

  • gapi.auth2.getAuthInstance()
  • GoogleUser.grant()

隱含流程

新增 TokenClientConfig 物件和 initTokenClient() 呼叫來設定網頁應用程式,按照初始化權杖用戶端中的範例操作。

取代 Google 登入 JavaScript 用戶端參考資料,改用 Google Identity 服務

物件:

  • gapi.auth2.AuthorizeConfig (使用 TokenClientConfig 付款)

方法:

  • gapi.auth2.init() (使用 google.accounts.oauth2.initTokenClient() 付款)

參數:

  • gapi.auth2.AuthorizeConfig.login_hintTokenClientConfig.login_hint
  • gapi.auth2.GoogleUser.getHostedDomain()TokenClientConfig.hd

授權碼流程

按照「初始化 Code 用戶端」中的範例,新增 CodeClientConfig 物件和 initCodeClient() 呼叫,以設定網路應用程式。

從隱含流程切換至授權碼流程時,請注意下列事項:

移除 Google 登入 JavaScript 用戶端參考資料

物件:

  • gapi.auth2.AuthorizeConfig

方法:

  • gapi.auth2.init()

參數:

  • gapi.auth2.AuthorizeConfig.login_hint
  • gapi.auth2.GoogleUser.getHostedDomain()

權杖要求

使用者手勢 (例如按下按鈕) 會產生要求,導致存取權存證直接透過隱含流程傳回使用者的瀏覽器,或是在使用者授權碼換取存取權存證和重新整理權證後,傳回後端平台。

隱含流程

當使用者登入並與 Google 建立有效工作階段時,您可以在瀏覽器中取得存取權權杖並加以使用。在隱含模式中,即使先前已提出要求,使用者仍必須透過手勢要求存取權存取權。

取代 Google 登入 JavaScript 用戶端參考資料:使用 Google 身分識別服務

方法:

  • gapi.auth2.authorize() (使用 TokenClient.requestAccessToken() 付款)
  • GoogleUser.reloadAuthResponse() 搭配 TokenClient.requestAccessToken()

新增連結或按鈕來呼叫 requestAccessToken(),以便啟動彈出式使用者體驗流程,要求存取權杖,或在現有權杖到期時取得新權杖。

將程式碼集更新為:

  • 使用 requestAccessToken() 觸發 OAuth 2.0 權杖流程
  • 使用 requestAccessTokenOverridableTokenClientConfig,將多個範圍的單一要求分割為多個較小的要求,以支援增量授權。
  • 在現有權杖過期或遭到撤銷時要求新權杖。

使用多個範圍可能需要對程式碼庫進行結構變更,以便在需要時要求存取範圍,而非一次要求所有範圍,這稱為漸進式授權。每個要求都應該包含盡可能少的範圍,最好是單一範圍。如要進一步瞭解如何更新應用程式以進行漸進式授權,請參閱如何處理使用者同意聲明

存取權杖到期時,gapi.auth2 模組會自動為網頁應用程式取得新的有效存取權杖。為了提升使用者安全性,Google Identity 服務程式庫不支援這個自動權杖重新整理程序。您必須更新網頁應用程式,才能偵測過期的存取權杖,並索取新的權杖。詳情請參閱下方的「符記處理」一節。

授權碼流程

新增連結或按鈕來呼叫 requestCode(),以便向 Google 索取授權碼。如需範例,請參閱「觸發 OAuth 2.0 授權碼流程」。

如要進一步瞭解如何回應已過期或已撤銷的存取權存取權,請參閱下方的「處理權杖」一節。

權杖處理

新增錯誤處理功能,以便在使用已過期或已撤銷的存取權杖時,偵測 Google API 呼叫失敗,並要求新的有效存取權杖。

使用已過期或已撤銷的存取權權杖時,Google API 會傳回 HTTP 狀態碼 401 Unauthorizedinvalid_token 錯誤訊息。如需範例,請參閱「無效的權杖回應」。

過期的權杖

存取權杖的有效期限很短,通常只有幾分鐘。

撤銷權杖

Google 帳戶擁有者隨時可以撤銷先前同意的授權。這麼做會使現有的存取權杖和更新權杖失效。系統可能會使用 revoke() 或透過 Google 帳戶從您的平台觸發撤銷程序。

取代 Google 登入 JavaScript 用戶端參考資料:使用 Google 身分識別服務

方法:

  • getAuthInstance().disconnect() (使用 google.accounts.oauth2.revoke() 付款)
  • GoogleUser.disconnect() (使用 google.accounts.oauth2.revoke() 付款)

當使用者在您的平台上刪除自己的帳戶時,請呼叫 revoke,或要求移除與應用程式共用資料的同意聲明。

如果您的網頁應用程式或後端平台要求存取權存取權杖,Google 就會向使用者顯示同意聲明對話方塊。請參閱 Google 向使用者顯示的同意聲明對話方塊範例。

向應用程式發出存取權權杖前,您必須先建立現有的有效 Google 工作階段,才能提示使用者同意並記錄結果。如果使用者尚未建立現有工作階段,系統可能會要求使用者登入 Google 帳戶。

使用者登入

使用者可以在不同的瀏覽器分頁中登入 Google 帳戶,也可以透過瀏覽器或作業系統登入。建議您在網站中加入「使用 Google 帳戶登入」功能,這樣一來,當使用者首次開啟應用程式時,系統就能在 Google 帳戶和瀏覽器之間建立有效的工作階段。這樣做可帶來以下好處:

  • 盡量減少使用者必須登入的次數,如果尚未有有效的工作階段,要求存取權存證就會啟動 Google 帳戶登入程序。
  • 直接使用 JWT ID 權杖憑證 email 欄位,做為 CodeClientConfigTokenClientConfig 物件中 login_hint 參數的值。如果您的平台未維護使用者帳戶管理系統,這項功能就特別實用。
  • 查詢 Google 帳戶,並將其與您平台上的現有本機使用者帳戶建立關聯,以盡量減少平台上的重複帳戶。
  • 建立新的本機帳戶時,註冊對話方塊和流程可以與使用者驗證對話方塊和流程明確區隔,減少所需步驟數並提高完成率。

在登入後,且在發出存取權存取金鑰之前,使用者必須針對應用程式要求的範圍提供同意聲明。

取得同意聲明後,系統會傳回存取權杖,以及使用者核准或拒絕的權限清單。

精細權限可讓使用者核准或拒絕個別範圍。要求存取多個範圍時,系統會獨立授予或拒絕每個範圍,不受其他範圍影響。根據使用者的選擇,應用程式會依個別範圍有選擇地啟用功能。

隱含流程

取代 Google 登入 JavaScript 用戶端參照資料,改用 Google Identity 服務

物件:

  • gapi.auth2.AuthorizeResponse (使用 TokenClient.TokenResponse 付款)
  • gapi.auth2.AuthResponse (使用 TokenClient.TokenResponse 付款)

方法:

  • GoogleUser.hasGrantedScopes() 搭配 google.accounts.oauth2.hasGrantedAllScopes()
  • GoogleUser.getGrantedScopes() 搭配 google.accounts.oauth2.hasGrantedAllScopes()

移除 Google 登入 JavaScript 用戶端參考資料

方法:

  • GoogleUser.getAuthResponse()

按照這個精細權限範例,使用 hasGrantedAllScopes()hasGrantedAnyScope() 更新網頁應用程式。

授權碼流程

按照「授權碼處理」中的操作說明,更新新增授權碼端點至後端平台。

更新平台,按照「使用程式碼模型」指南中的步驟驗證要求,並取得存取權杖和重新整理權杖。

更新平台,根據使用者核准的個別範圍,有選擇地啟用或停用功能,方法是按照逐步授權檢查使用者授予的存取範圍的操作說明進行。

隱含流程範例

傳統做法

GAPI 用戶端程式庫

JavaScript 專用 Google API 用戶端程式庫的範例 (透過顯示使用者同意的彈出式對話方塊),在瀏覽器中執行。

gapi.auth2 模組會自動載入並由 gapi.client.init() 使用,因此會隱藏。

<!DOCTYPE html>
  <html>
    <head>
      <script src="https://apis.google.com/js/api.js"></script>
      <script>
        function start() {
          gapi.client.init({
            'apiKey': 'YOUR_API_KEY',
            'clientId': 'YOUR_CLIENT_ID',
            'scope': 'https://www.googleapis.com/auth/cloud-translation',
            'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
          }).then(function() {
            // Execute an API request which is returned as a Promise.
            // The method name language.translations.list comes from the API discovery.
            return gapi.client.language.translations.list({
              q: 'hello world',
              source: 'en',
              target: 'de',
            });
          }).then(function(response) {
            console.log(response.result.data.translations[0].translatedText);
          }, function(reason) {
            console.log('Error: ' + reason.result.error.message);
          });
        };

        // Load the JavaScript client library and invoke start afterwards.
        gapi.load('client', start);
      </script>
    </head>
    <body>
      <div id="results"></div>
    </body>
  </html>

JS 用戶端程式庫

用於用戶端網頁應用程式的 OAuth 2.0,在瀏覽器中執行,並使用彈出式對話方塊取得使用者同意聲明。

手動載入 gapi.auth2 模組。

<!DOCTYPE html>
<html><head></head><body>
<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>
</body></html>

OAuth 2.0 端點

在瀏覽器中執行的用戶端網頁應用程式 OAuth 2.0,使用重新導向至 Google 的功能取得使用者同意。

這個範例會顯示從使用者瀏覽器直接呼叫 Google OAuth 2.0 端點,且不會使用 gapi.auth2 模組或 JavaScript 程式庫。

<!DOCTYPE html>
<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>

新做法

僅限 GIS

本範例只顯示使用憑證模型的 Google Identity 服務 JavaScript 程式庫,以及表示使用者同意的彈出式對話方塊。這項工具可說明設定用戶端、要求及取得存取權杖,以及呼叫 Google API 所需的最低步驟數。

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      var access_token;

      function initClient() {
        client = google.accounts.oauth2.initTokenClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly \
                  https://www.googleapis.com/auth/contacts.readonly',
          callback: (tokenResponse) => {
            access_token = tokenResponse.access_token;
          },
        });
      }
      function getToken() {
        client.requestAccessToken();
      }
      function revokeToken() {
        google.accounts.oauth2.revoke(access_token, () => {console.log('access token revoked')});
      }
      function loadCalendar() {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'https://www.googleapis.com/calendar/v3/calendars/primary/events');
        xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
        xhr.send();
      }
    </script>
    <h1>Google Identity Services Authorization Token model</h1>
    <button onclick="getToken();">Get access token</button><br><br>
    <button onclick="loadCalendar();">Load Calendar</button><br><br>
    <button onclick="revokeToken();">Revoke token</button>
  </body>
</html>

GAPI 的 async/await

本範例說明如何使用權杖模型新增 Google Identity Service 程式庫、移除 gapi.auth2 模組,以及使用 JavaScript 專用 Google API 用戶端程式庫呼叫 API。

Promise、async 和 await 是用來強制執行程式庫載入順序,以及擷取及重試授權錯誤。API 呼叫只有在有效的存取權杖可用時才會發出。

當使用者首次載入網頁時缺少存取權權杖,或在存取權權杖到期後,使用者應按下「Show Calendar」(顯示行事曆) 按鈕。

<!DOCTYPE html>
<html>
<head></head>
<body>
  <h1>GAPI with GIS async/await</h1>
  <button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br>
  <button id="revokeBtn" onclick="revokeToken();">Revoke access token</button>

  <script>

    const gapiLoadPromise = new Promise((resolve, reject) => {
      gapiLoadOkay = resolve;
      gapiLoadFail = reject;
    });
    const gisLoadPromise = new Promise((resolve, reject) => {
      gisLoadOkay = resolve;
      gisLoadFail = reject;
    });

    var tokenClient;

    (async () => {
      document.getElementById("showEventsBtn").style.visibility="hidden";
      document.getElementById("revokeBtn").style.visibility="hidden";

      // First, load and initialize the gapi.client
      await gapiLoadPromise;
      await new Promise((resolve, reject) => {
        // NOTE: the 'auth2' module is no longer loaded.
        gapi.load('client', {callback: resolve, onerror: reject});
      });
      await gapi.client.init({
        // NOTE: OAuth2 'scope' and 'client_id' parameters have moved to initTokenClient().
      })
      .then(function() {  // Load the Calendar API discovery document.
        gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
      });

      // Now load the GIS client
      await gisLoadPromise;
      await new Promise((resolve, reject) => {
        try {
          tokenClient = google.accounts.oauth2.initTokenClient({
              client_id: 'YOUR_CLIENT_ID',
              scope: 'https://www.googleapis.com/auth/calendar.readonly',
              prompt: 'consent',
              callback: '',  // defined at request time in await/promise scope.
          });
          resolve();
        } catch (err) {
          reject(err);
        }
      });

      document.getElementById("showEventsBtn").style.visibility="visible";
      document.getElementById("revokeBtn").style.visibility="visible";
    })();

    async function getToken(err) {

      if (err.result.error.code == 401 || (err.result.error.code == 403) &&
          (err.result.error.status == "PERMISSION_DENIED")) {

        // The access token is missing, invalid, or expired, prompt for user consent to obtain one.
        await new Promise((resolve, reject) => {
          try {
            // Settle this promise in the response callback for requestAccessToken()
            tokenClient.callback = (resp) => {
              if (resp.error !== undefined) {
                reject(resp);
              }
              // GIS has automatically updated gapi.client with the newly issued access token.
              console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
              resolve(resp);
            };
            tokenClient.requestAccessToken();
          } catch (err) {
            console.log(err)
          }
        });
      } else {
        // Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
        throw new Error(err);
      }
    }

    function showEvents() {

      // Try to fetch a list of Calendar events. If a valid access token is needed,
      // prompt to obtain one and then retry the original request.
      gapi.client.calendar.events.list({ 'calendarId': 'primary' })
      .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
      .catch(err  => getToken(err))  // for authorization errors obtain an access token
      .then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' }))
      .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
      .catch(err  => console.log(err)); // cancelled by user, timeout, etc.
    }

    function revokeToken() {
      let cred = gapi.client.getToken();
      if (cred !== null) {
        google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)});
        gapi.client.setToken('');
      }
    }

  </script>

  <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoadOkay()" onerror="gapiLoadFail(event)"></script>
  <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoadOkay()" onerror="gisLoadFail(event)"></script>

</body>
</html>

GAPI 回呼

本例說明如何使用權杖模型新增 Google Identity 服務程式庫、移除 gapi.auth2 模組,以及使用適用於 JavaScript 的 Google API 用戶端程式庫呼叫 API。

變數可用於強制執行程式庫載入順序。GAPI 呼叫會在系統傳回有效的存取權杖後,透過回呼中的 提出。

使用者應在網頁首次載入時按下「顯示日曆」按鈕,並在想要重新整理日曆資訊時再次按下該按鈕。

<!DOCTYPE html>
<html>
<head>
  <script async defer src="https://apis.google.com/js/api.js" onload="gapiLoad()"></script>
  <script async defer src="https://accounts.google.com/gsi/client" onload="gisInit()"></script>
</head>
<body>
  <h1>GAPI with GIS callbacks</h1>
  <button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br>
  <button id="revokeBtn" onclick="revokeToken();">Revoke access token</button>
  <script>
    let tokenClient;
    let gapiInited;
    let gisInited;

    document.getElementById("showEventsBtn").style.visibility="hidden";
    document.getElementById("revokeBtn").style.visibility="hidden";

    function checkBeforeStart() {
       if (gapiInited && gisInited){
          // Start only when both gapi and gis are initialized.
          document.getElementById("showEventsBtn").style.visibility="visible";
          document.getElementById("revokeBtn").style.visibility="visible";
       }
    }

    function gapiInit() {
      gapi.client.init({
        // NOTE: OAuth2 'scope' and 'client_id' parameters have moved to initTokenClient().
      })
      .then(function() {  // Load the Calendar API discovery document.
        gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
        gapiInited = true;
        checkBeforeStart();
      });
    }

    function gapiLoad() {
        gapi.load('client', gapiInit)
    }

    function gisInit() {
     tokenClient = google.accounts.oauth2.initTokenClient({
                client_id: 'YOUR_CLIENT_ID',
                scope: 'https://www.googleapis.com/auth/calendar.readonly',
                callback: '',  // defined at request time
            });
      gisInited = true;
      checkBeforeStart();
    }

    function showEvents() {

      tokenClient.callback = (resp) => {
        if (resp.error !== undefined) {
          throw(resp);
        }
        // GIS has automatically updated gapi.client with the newly issued access token.
        console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));

        gapi.client.calendar.events.list({ 'calendarId': 'primary' })
        .then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
        .catch(err => console.log(err));

        document.getElementById("showEventsBtn").innerText = "Refresh Calendar";
      }

      // Conditionally ask users to select the Google Account they'd like to use,
      // and explicitly obtain their consent to fetch their Calendar.
      // NOTE: To request an access token a user gesture is necessary.
      if (gapi.client.getToken() === null) {
        // Prompt the user to select a Google Account and asked for consent to share their data
        // when establishing a new session.
        tokenClient.requestAccessToken({prompt: 'consent'});
      } else {
        // Skip display of account chooser and consent dialog for an existing session.
        tokenClient.requestAccessToken({prompt: ''});
      }
    }

    function revokeToken() {
      let cred = gapi.client.getToken();
      if (cred !== null) {
        google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)});
        gapi.client.setToken('');
        document.getElementById("showEventsBtn").innerText = "Show Calendar";
      }
    }
  </script>
</body>
</html>

授權碼流程範例

Google Identity Service 程式庫彈出式使用者體驗可使用網址重新導向,直接將授權碼傳回後端符記端點,或是在使用者瀏覽器中執行的 JavaScript 回呼處理常式,將回應代理至您的平台。無論是哪一種情況,您的後端平台都會完成 OAuth 2.0 流程,取得有效的重新整理和存取權杖。

傳統做法

伺服器端網頁應用程式

伺服器端應用程式的 Google 登入功能,在後端平台上執行,使用重新導向至 Google 的功能取得使用者同意。

<!DOCTYPE html>
<html>
  <head>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
    <script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
    <script>
      function start() {
        gapi.load('auth2', function() {
          auth2 = gapi.auth2.init({
            client_id: 'YOUR_CLIENT_ID',
            api_key: 'YOUR_API_KEY',
            discovery_docs: ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
            // Scopes to request in addition to 'profile' and 'email'
            scope: 'https://www.googleapis.com/auth/cloud-translation',
          });
        });
      }
      function signInCallback(authResult) {
        if (authResult['code']) {
          console.log("sending AJAX request");
          // Send authorization code obtained from Google to backend platform
          $.ajax({
            type: 'POST',
            url: 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URL',
            // Always include an X-Requested-With header to protect against CSRF attacks.
            headers: {
              'X-Requested-With': 'XMLHttpRequest'
            },
            contentType: 'application/octet-stream; charset=utf-8',
            success: function(result) {
              console.log(result);
            },
            processData: false,
            data: authResult['code']
          });
        } else {
          console.log('error: failed to obtain authorization code')
        }
      }
    </script>
  </head>
  <body>
    <button id="signinButton">Sign In With Google</button>
    <script>
      $('#signinButton').click(function() {
        // Obtain an authorization code from Google
        auth2.grantOfflineAccess().then(signInCallback);
      });
    </script>
  </body>
</html>

使用重新導向的 HTTP/REST

針對網路伺服器應用程式使用 OAuth 2.0,將授權碼從使用者的瀏覽器傳送至後端平台。使用者同意聲明處理方式:將使用者的瀏覽器重新導向至 Google。

/\*
 \* 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 &lt;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_AUTHORIZATION_CODE_ENDPOINT_URL',
                '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();
}

新做法

GIS 彈出式視窗使用者體驗

這個範例只會顯示 Google Identity 服務 JavaScript 程式庫,使用授權碼模型,提供使用者同意的彈出式對話方塊,以及回呼處理常式,以便接收 Google 的授權碼。這項資訊可說明設定用戶端、取得同意聲明,以及將授權碼傳送至後端平台所需的最低步驟數。

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      function initClient() {
        client = google.accounts.oauth2.initCodeClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly',
          ux_mode: 'popup',
          callback: (response) => {
            var code_receiver_uri = 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URI',
            // Send auth code to your backend platform
            const xhr = new XMLHttpRequest();
            xhr.open('POST', code_receiver_uri, true);
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.onload = function() {
              console.log('Signed in as: ' + xhr.responseText);
            };
            xhr.send('code=' + response.code);
            // After receipt, the code is exchanged for an access token and
            // refresh token, and the platform then updates this web app
            // running in user's browser with the requested calendar info.
          },
        });
      }
      function getAuthCode() {
        // Request authorization code and obtain user consent
        client.requestCode();
      }
    </script>
    <button onclick="getAuthCode();">Load Your Calendar</button>
  </body>
</html>

GIS Redirect UX

授權碼模型支援彈出式和重新導向 UX 模式,可將每位使用者的授權碼傳送至平台代管的端點。重新導向使用者體驗模式如下所示:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
  </head>
  <body>
    <script>
      var client;
      function initClient() {
        client = google.accounts.oauth2.initCodeClient({
          client_id: 'YOUR_CLIENT_ID',
          scope: 'https://www.googleapis.com/auth/calendar.readonly \
                  https://www.googleapis.com/auth/photoslibrary.readonly',
          ux_mode: 'redirect',
          redirect_uri: 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URI'
        });
      }
      // Request an access token
      function getAuthCode() {
        // Request authorization code and obtain user consent
        client.requestCode();
      }
    </script>
    <button onclick="getAuthCode();">Load Your Calendar</button>
  </body>
</html>

JavaScript 程式庫

Google Identity 服務是用於使用者驗證和授權的單一 JavaScript 程式庫,可整合並取代多個不同程式庫和模組中的功能:

遷移至 Identity 服務時的建議做法:

現有的 JS 程式庫 新的 JS 程式庫 附註
apis.google.com/js/api.js accounts.google.com/gsi/client 新增程式庫,並按照隱含流程操作。
apis.google.com/js/client.js accounts.google.com/gsi/client 新增程式庫和授權碼流程。

程式庫快速參考

舊版 Google 登入 JavaScript 用戶端程式庫與新版 Google Identity 服務程式庫之間的物件和方法比較,以及附註,提供額外資訊和遷移期間應採取的動作。

新增 附註
GoogleAuth 物件和相關方法:
GoogleAuth.attachClickHandler() 移除
GoogleAuth.currentUser.get() 移除
GoogleAuth.currentUser.listen() 移除
GoogleAuth.disconnect() google.accounts.oauth2.revoke 更換為新舊裝置。您也可以前往 https://myaccount.google.com/permissions 撤銷授權。
GoogleAuth.grantOfflineAccess() 移除,按照授權碼流程操作。
GoogleAuth.isSignedIn.get() 移除
GoogleAuth.isSignedIn.listen() 移除
GoogleAuth.signIn() 移除
GoogleAuth.signOut() 移除
GoogleAuth.then() 移除
GoogleUser 物件和相關方法:
GoogleUser.disconnect() google.accounts.id.revoke 以新內容取代舊內容。您也可以前往 https://myaccount.google.com/permissions 撤銷授權。
GoogleUser.getAuthResponse() requestCode() or requestAccessToken() 以新內容取代舊內容
GoogleUser.getBasicProfile() ,即可逐一移除離線觀看清單內的影片;請改用 ID 權杖,詳情請參閱「從 Google 登入遷移」一文。
GoogleUser.getGrantedScopes() hasGrantedAnyScope() 以新內容取代舊內容
GoogleUser.getHostedDomain() 移除
GoogleUser.getId() 移除
GoogleUser.grantOfflineAccess() 移除後,請按照授權碼流程操作。
GoogleUser.grant() 移除
GoogleUser.hasGrantedScopes() hasGrantedAnyScope() 以新內容取代舊內容
GoogleUser.isSignedIn() 移除
GoogleUser.reloadAuthResponse() requestAccessToken() 移除舊的,呼叫新的,以取代已過期或已撤銷的存取權權杖。
gapi.auth2 物件和相關方法:
gapi.auth2.AuthorizeConfig 物件 TokenClientConfig 或 CodeClientConfig 以新內容取代舊內容
gapi.auth2.AuthorizeResponse 物件 移除
gapi.auth2.AuthResponse 物件 移除
gapi.auth2.authorize() requestCode() or requestAccessToken() 以新內容取代舊內容
gapi.auth2.ClientConfig() TokenClientConfig 或 CodeClientConfig 以新內容取代舊內容
gapi.auth2.getAuthInstance() 移除
gapi.auth2.init() initTokenClient() or initCodeClient() 以新內容取代舊內容
gapi.auth2.OfflineAccessOptions 物件 移除
gapi.auth2.SignInOptions 物件 移除
gapi.signin2 物件和相關方法:
gapi.signin2.render() ,即可逐一移除離線觀看清單內的影片;使用 HTML DOM 載入 g_id_signin 元素,或對 google.accounts.id.renderButton 發出 JS 呼叫,即可觸發使用者登入 Google 帳戶。

憑證範例

現有憑證

Google 登入平台程式庫適用於 JavaScript 的 Google API 用戶端程式庫,或直接呼叫 Google Auth 2.0 端點,都會在單一回應中傳回 OAuth 2.0 存取權杖和 OpenID Connect ID 權杖。

回應範例:同時包含 access_tokenid_token

  {
    "token_type": "Bearer",
    "access_token": "ya29.A0ARrdaM-SmArZaCIh68qXsZSzyeU-8mxhQERHrP2EXtxpUuZ-3oW8IW7a6D2J6lRnZrRj8S6-ZcIl5XVEqnqxq5fuMeDDH_6MZgQ5dgP7moY-yTiKR5kdPm-LkuPM-mOtUsylWPd1wpRmvw_AGOZ1UUCa6UD5Hg",
    "scope": "https://www.googleapis.com/auth/calendar.readonly",
    "login_hint": "AJDLj6I2d1RH77cgpe__DdEree1zxHjZJr4Q7yOisoumTZUmo5W2ZmVFHyAomUYzLkrluG-hqt4RnNxrPhArd5y6p8kzO0t8xIfMAe6yhztt6v2E-_Bb4Ec3GLFKikHSXNh5bI-gPrsI",
    "expires_in": 3599,
    "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjkzNDFhYmM0MDkyYjZmYzAzOGU0MDNjOTEwMjJkZDNlNDQ1MzliNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXpwIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTE3NzI2NDMxNjUxOTQzNjk4NjAwIiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJkYWJyaWFuQGdvb2dsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IkJBSW55TjN2MS1ZejNLQnJUMVo0ckEiLCJuYW1lIjoiQnJpYW4gRGF1Z2hlcnR5IiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hLS9BT2gxNEdnenAyTXNGRGZvbVdMX3VDemRYUWNzeVM3ZGtxTE5ybk90S0QzVXNRPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IkJyaWFuIiwiZmFtaWx5X25hbWUiOiJEYXVnaGVydHkiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTYzODk5MTYzOCwiZXhwIjoxNjM4OTk1MjM4LCJqdGkiOiI5YmRkZjE1YWFiNzE2ZDhjYmJmNDYwMmM1YWM3YzViN2VhMDQ5OTA5In0.K3EA-3Adw5HA7O8nJVCsX1HmGWxWzYk3P7ViVBb4H4BoT2-HIgxKlx1mi6jSxIUJGEekjw9MC-nL1B9Asgv1vXTMgoGaNna0UoEHYitySI23E5jaMkExkTSLtxI-ih2tJrA2ggfA9Ekj-JFiMc6MuJnwcfBTlsYWRcZOYVw3QpdTZ_VYfhUu-yERAElZCjaAyEXLtVQegRe-ymScra3r9S92TA33ylMb3WDTlfmDpWL0CDdDzby2asXYpl6GQ7SdSj64s49Yw6mdGELZn5WoJqG7Zr2KwIGXJuSxEo-wGbzxNK-mKAiABcFpYP4KHPEUgYyz3n9Vqn2Tfrgp-g65BQ",
    "session_state": {
      "extraQueryParams": {
        "authuser": "0"
      }
    },
    "first_issued_at": 1638991637982,
    "expires_at": 1638995236982,
    "idpId": "google"
  }

Google Identity 服務憑證

Google Identity Services 程式庫會傳回:

  • 存取權杖 (用於授權):

    {
      "access_token": "ya29.A0ARrdaM_LWSO-uckLj7IJVNSfnUityT0Xj-UCCrGxFQdxmLiWuAosnAKMVQ2Z0LLqeZdeJii3TgULp6hR_PJxnInBOl8UoUwWoqsrGQ7-swxgy97E8_hnzfhrOWyQBmH6zs0_sUCzwzhEr_FAVqf92sZZHphr0g",
      "token_type": "Bearer",
      "expires_in": 3599,
      "scope": "https://www.googleapis.com/auth/calendar.readonly"
    }
    
  • 或用於驗證的 ID 權杖:

    {
      "clientId": "538344653255-758c5h5isc45vgk27d8h8deabovpg6to.apps.googleusercontent.com",
      "credential": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImMxODkyZWI0OWQ3ZWY5YWRmOGIyZTE0YzA1Y2EwZDAzMjcxNGEyMzciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2MzkxNTcyNjQsImF1ZCI6IjUzODM0NDY1MzI1NS03NThjNWg1aXNjNDV2Z2syN2Q4aDhkZWFib3ZwZzZ0by5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjExNzcyNjQzMTY1MTk0MzY5ODYwMCIsIm5vbmNlIjoiZm9vYmFyIiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJkYWJyaWFuQGdvb2dsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwibmFtZSI6IkJyaWFuIERhdWdoZXJ0eSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQU9oMTRHZ3pwMk1zRkRmb21XTF91Q3pkWFFjc3lTN2RrcUxOcm5PdEtEM1VzUT1zOTYtYyIsImdpdmVuX25hbWUiOiJCcmlhbiIsImZhbWlseV9uYW1lIjoiRGF1Z2hlcnR5IiwiaWF0IjoxNjM5MTU3NTY0LCJleHAiOjE2MzkxNjExNjQsImp0aSI6IjRiOTVkYjAyZjU4NDczMmUxZGJkOTY2NWJiMWYzY2VhYzgyMmI0NjUifQ.Cr-AgMsLFeLurnqyGpw0hSomjOCU4S3cU669Hyi4VsbqnAV11zc_z73o6ahe9Nqc26kPVCNRGSqYrDZPfRyTnV6g1PIgc4Zvl-JBuy6O9HhClAK1HhMwh1FpgeYwXqrng1tifmuotuLQnZAiQJM73Gl-J_6s86Buo_1AIx5YAKCucYDUYYdXBIHLxrbALsA5W6pZCqqkMbqpTWteix-G5Q5T8LNsfqIu_uMBUGceqZWFJALhS9ieaDqoxhIqpx_89QAr1YlGu_UO6R6FYl0wDT-nzjyeF5tonSs3FHN0iNIiR3AMOHZu7KUwZaUdHg4eYkU-sQ01QNY_11keHROCRQ",
      "select_by": "user"
    }
    

權杖回應無效

嘗試使用已過期、已撤銷或無效的存取權權杖提出 API 要求時,Google 的回應範例:

HTTP 回應標頭

  www-authenticate: Bearer realm="https://accounts.google.com/", error="invalid_token"

回應主體

  {
    "error": {
      "code": 401,
      "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
      "errors": [
        {
          "message": "Invalid Credentials",
          "domain": "global",
          "reason": "authError",
          "location": "Authorization",
          "locationType": "header"
        }
      ],
      "status": "UNAUTHENTICATED"
    }
  }