使用符記模型

google.accounts.oauth2 JavaScript 程式庫可協助您提示使用者同意,並取得存取權杖來處理使用者資料。這項服務以 OAuth 2.0 隱含授權流程為基礎,可讓您直接使用 REST 和 CORS 呼叫 Google API,也可以使用 JavaScript 適用的 Google API 用戶端程式庫 (也稱為 gapi.client),輕鬆彈性地存取較複雜的 API。

從瀏覽器存取受保護的使用者資料前,網站上的使用者會觸發 Google 的網頁版帳戶選擇器、登入和同意程序,最後 Google 的 OAuth 伺服器會核發存取權杖,並傳回給您的網路應用程式。

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

建議您採用本文所述方法,而非舊版「針對用戶端網頁應用程式使用 OAuth 2.0」指南涵蓋的技術。

必要條件

按照「設定」一節所述步驟,設定 OAuth 同意畫面、取得用戶端 ID,並載入用戶端程式庫。

初始化權杖用戶端

呼叫 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() 方法觸發權杖 UX 流程,並取得存取權杖。Google 會提示使用者:

  • 選擇帳戶,
  • 登入 Google 帳戶 (若當下尚未登入),
  • 同意網頁應用程式存取每個要求的範圍。

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

接著,Google 會將 TokenResponse (內含存取權權杖和使用者已授予存取權的範圍清單) 或錯誤傳回給回呼處理常式。

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

請詳閱 Google 的 OAuth 2.0 政策,再實作應用程式的設計和使用者體驗。這些政策涵蓋使用多個範圍、何時及如何處理使用者同意聲明等主題。

增量授權是一種政策和應用程式設計方法,可讓您只在需要時使用範圍要求資源存取權,而不是預先一次要求所有權限。使用者可以核准或拒絕分享應用程式要求的個別資源,這稱為「細部權限」

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

但也有例外情況。如果 Google Workspace Enterprise 應用程式具有網域範圍的授權委派,或是標示為「可信任」,系統就會略過詳細權限同意畫面。對於這類應用程式,使用者不會看到適當的權限同意畫面。應用程式會收到所有要求的範圍,或完全沒有。

如需更多詳細資訊,請參閱「如何處理精細權限」。

增量授權

如果是網路應用程式,以下兩個高階情境會示範如何使用下列項目進行增量授權:

  • 單頁 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,在每次載入網頁時要求同意聲明。

使用權杖

在權杖模型中,作業系統或瀏覽器不會儲存存取權杖,而是在網頁載入時首次取得新權杖,或是在使用者手勢 (例如按下按鈕) 觸發對 requestAccessToken() 的呼叫時取得新權杖。

搭配使用 REST 和 CORS 與 Google API

存取權杖可用於透過 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 程式庫

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

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