使用符記模型

google.accounts.oauth2 JavaScript 程式庫可協助您提示使用者表示同意,並取得與使用者資料搭配使用的存取權杖。這個 API 以 OAuth 2.0 隱含授權流程為基礎,旨在讓您透過 REST 和 CORS 直接呼叫 Google API,或是透過 JavaScript 專用的 Google API 用戶端程式庫 (也稱為 gapi.client) 輕鬆靈活地存取我們較複雜的 API。

透過瀏覽器存取受保護的使用者資料之前,網站使用者會觸發 Google 的網頁式帳戶選擇工具、登入和同意聲明程序,最後再由 Google 的 OAuth 伺服器發出存取權杖,並將存取權杖傳回至網頁應用程式。

在權杖式授權模型中,您不需要在後端伺服器上儲存每位使用者的更新權杖。

建議您採用本文所述方法,而非舊版適用於用戶端網頁應用程式的 OAuth 2.0 指南中提及的技術。

設定

按照「取得 Google API 用戶端 ID」指南所述的步驟,尋找或建立用戶端 ID。接下來,請在網站中呼叫 Google API 的網頁中新增用戶端程式庫。最後,初始化權杖用戶端。一般而言,這項作業會在用戶端程式庫的 onload 處理常式內完成。

初始化權杖用戶端

呼叫 initTokenClient(),使用網頁應用程式的用戶端 ID 初始化新的權杖用戶端。您也可以選擇加入一份清單,列出使用者需要存取的一或多個「範圍」

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  callback: (response) => {
    ...
  },
});

觸發 OAuth 2.0 權杖流程

使用 requestAccessToken() 方法觸發權杖使用者體驗流程,並取得存取權杖。Google 會提示使用者執行下列操作:

  • 選擇他們的帳戶。
  • 登入 Google 帳戶 (如果尚未登入)
  • 授權網頁應用程式存取每個要求的範圍。

使用者手勢會觸發權杖流程:<button onclick="client.requestAccessToken();">Authorize me</button>

接著,Google 會傳回 TokenResponse,其中包含使用者已授予回呼處理常式存取權或錯誤的範圍清單。

使用者可以關閉帳戶選擇工具或登入視窗,在這種情況下,系統不會叫用您的回呼函式。

建議您只在充分審查 Google 的 OAuth 2.0 政策之後,才實作應用程式的設計和使用者體驗。這些政策涵蓋處理多個範圍、處理使用者同意聲明的時機和方式等。

「增量授權」是一種政策和應用程式設計方法,用於要求存取資源,只在必要時使用範圍,而非預先授權且全部使用。使用者可以核准或拒絕應用程式要求的個別資源共用,這就是所謂的精細權限

在這個程序中,Google 會提示使用者同意,請逐一列出每個要求的範圍,使用者選取要與應用程式共用的資源,最後再叫用回呼函式,傳回存取權杖和使用者核准的範圍。接著,您的應用程式會使用精細的權限,安全地處理各種不同結果。

增量授權

如果是網頁應用程式,以下兩種高階情境示範了使用以下兩種高階情境示範:

  • 單頁 Ajax 應用程式,通常使用 XMLHttpRequest 以動態存取資源。
  • 多個網頁和資源會分開處理,並按網頁進行管理。

這兩個情境旨在說明設計考量和方法,但不是針對如何在應用程式中建立同意聲明的完整建議。實際應用程式可能會利用這些技術的變化版本或組合。

阿賈克斯隊 (Ajax)

如要新增對應用程式漸進式授權的支援功能,請多次呼叫 requestAccessToken(),並使用 OverridableTokenClientConfig 物件的 scope 參數在需要時,並且僅在必要時才要求個別範圍。在本例中,只有在使用者手勢展開收合的內容區段後,系統才會要求並顯示資源。

Ajax 應用程式
在載入網頁時初始化權杖用戶端:
        const client = google.accounts.oauth2.initTokenClient({
          client_id: 'YOUR_GOOGLE_CLIENT_ID',
          callback: "onTokenResponse",
        });
      
透過使用者手勢要求同意聲明並取得存取權杖,按一下「+」即可開啟:

要閱讀的文件

顯示最近的文件

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/documents.readonly'
             })
           );
        

近期活動

顯示日曆資訊

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/calendar.readonly'
             })
           );
        

展示相片

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/photoslibrary.readonly'
             })
           );
        

每次呼叫 requestAccessToken 都會觸發使用者同意時間,您的應用程式只能存取使用者選擇展開的部分所需的資源,因此可透過使用者選擇的方式分享資源。

多個網頁

設計漸進式授權時,系統會使用多個頁面,只要求載入網頁所需的範圍,因此可降低複雜度,且需要進行多次呼叫來取得使用者同意及擷取存取權杖。

多頁面應用程式
網頁 程式碼
第 1 頁:要閱讀的文件
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/documents.readonly',
  });
  client.requestAccessToken();
          
第 2 頁:近期活動
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/calendar.readonly',
  });
  client.requestAccessToken();
          
第 3 頁:相片輪轉介面
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/photoslibrary.readonly',
  });
  client.requestAccessToken();
          

每個頁面都會在載入時呼叫 initTokenClient()requestAccessToken(),以要求必要的範圍並取得存取權杖。在這種情況下,系統會使用個別網頁,根據範圍清楚區隔使用者功能和資源。在實際情況下,個別頁面可能會要求多個相關的範圍。

精細權限

在所有情況下,精細權限的處理方式都相同;在 requestAccessToken() 叫用回呼函式並傳回的存取權杖後,請檢查使用者是否已使用 hasGrantedAllScopes()hasGrantedAnyScope() 核准要求的範圍。例如:

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly \
          https://www.googleapis.com/auth/documents.readonly \
          https://www.googleapis.com/auth/photoslibrary.readonly',
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) {
      if (google.accounts.oauth2.hasGrantedAnyScope(tokenResponse,
          'https://www.googleapis.com/auth/photoslibrary.readonly')) {
        // Look at pictures
        ...
      }
      if (google.accounts.oauth2.hasGrantedAllScopes(tokenResponse,
          'https://www.googleapis.com/auth/calendar.readonly',
          'https://www.googleapis.com/auth/documents.readonly')) {
        // Meeting planning and review documents
        ...
      }
    }
  },
});

先前接受的任何工作階段或要求授予的授予內容,也會納入回應中。使用者同意聲明記錄會依使用者和用戶端 ID 保留,並會在多次呼叫 initTokenClient()requestAccessToken() 時保留。根據預設,只有在使用者首次造訪網站並要求新範圍時,才需要使用者同意聲明,但每次載入網頁時,都必須在權杖用戶端設定物件中使用 prompt=consent 提出要求。

使用權杖

在權杖模型中,OS 或瀏覽器不會儲存存取權杖,而是在載入網頁時先取得新權杖,或是在之後透過使用者手勢 (例如按下按鈕) 觸發 requestAccessToken() 呼叫。

搭配 Google API 使用 REST 和 CORS

存取權杖可用來透過 REST 和 CORS 對 Google API 發出通過驗證的要求。完成上述步驟後,使用者就能透過登入、同意授權,以及讓 Google 核發存取權杖和您的網站,以便處理使用者資料。

在此範例中,使用 tokenRequest() 傳回的存取權杖,查看已登入使用者即將進行的日曆活動:

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.googleapis.com/calendar/v3/calendars/primary/events');
xhr.setRequestHeader('Authorization', 'Bearer ' + tokenResponse.access_token);
xhr.send();

詳情請參閱「如何使用 CORS 存取 Google API」。

下一節將說明如何輕鬆整合更複雜的 API。

使用 Google API JavaScript 程式庫

權杖用戶端可與適用於 JavaScript 的 Google API 用戶端程式庫搭配使用,請參閱下列程式碼片段。

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) {
      gapi.client.setApiKey('YOUR_API_KEY');
      gapi.client.load('calendar', 'v3', listUpcomingEvents);
    }
  },
});

function listUpcomingEvents() {
  gapi.client.calendar.events.list(...);
}

權杖到期

在設計上,存取權杖的生命週期很短。如果存取權杖已在使用者工作階段結束前過期,請透過使用者導向的事件 (例如按下按鈕) 呼叫 requestAccessToken(),以取得新權杖。

呼叫 google.accounts.oauth2.revoke 方法,即可針對應用程式的所有授予範圍,移除使用者同意聲明並存取資源存取權。需要有效的存取權杖才能撤銷這項權限:

google.accounts.oauth2.revoke('414a76cb127a7ece7ee4bf287602ca2b56f8fcbf7fcecc2cd4e0509268120bd7', done => {
    console.log(done);
    console.log(done.successful);
    console.log(done.error);
    console.log(done.error_description);
  });