如何處理精細的權限

總覽

有了精細權限,消費者就能更精確地控管要與各個應用程式分享哪些帳戶資料。這項功能可提供更完善的控制、透明度和安全性,對使用者和開發人員都有好處。本指南將協助您瞭解必要的變更和步驟,成功更新應用程式,以便處理精細權限。

什麼是精細權限?

假設您開發了一款效率提升應用程式,需要要求電子郵件和日曆範圍。使用者可能只想將您的應用程式用於 Google 日曆,而非 Gmail。有了精細的 OAuth 權限,使用者可以選擇只授予 Google 日曆權限,而不授予 Gmail 權限。使用者可以授予特定資料的存取權,盡可能減少資料外洩、提升信任度,並以隱私權優先的方式控管數位生活。設計應用程式時,請務必考量如何處理這類情況。

要求多個非登入範圍時

登入和非登入範圍

如果應用程式同時要求登入和非登入範圍,使用者會先看到登入範圍 (email、profile 和 openid) 的同意聲明頁面。使用者同意分享基本身分資訊 (姓名、電子郵件地址和個人資料相片) 後,就會看到非登入範圍的精細權限同意聲明畫面。在這種情況下,應用程式必須檢查使用者授予的範圍,不能假設使用者會授予所有要求的範圍。在以下範例中,網頁應用程式要求所有三個登入範圍,以及 Google 雲端硬碟非登入範圍。使用者同意登入範圍後,會看到 Google 雲端硬碟權限的精細權限同意畫面:

登入和非登入範圍

多個非登入範圍

當應用程式要求多個非登入範圍時,系統會向使用者顯示精細權限同意畫面。使用者可以選取要核准與應用程式共用的權限。以下是精細權限同意畫面範例,要求存取使用者的 Gmail 郵件和 Google 日曆資料:

多個非登入範圍

如果應用程式只要求登入範圍 (email、profile 和 openid),則不適用精細權限同意畫面。使用者可以核准或拒絕整個登入要求,換句話說,如果應用程式只要求登入範圍 (一、二或全部三個),則不適用精細權限同意畫面。

如果應用程式只要求一個非登入範圍,則不適用精細權限同意畫面。換句話說,使用者只能核准或拒絕整個要求,同意畫面中不會顯示核取方塊。下表彙整了顯示精細權限同意畫面的時機。

登入範圍數量 非登入範圍數量 精細權限同意畫面
1-3 0 不適用
1-3 1+ 支援
0 1 不適用
0 2+ 支援

判斷應用程式是否受到影響

徹底檢查應用程式中,所有使用 Google OAuth 2.0 授權端點要求權限的部分。請特別留意要求多個範圍的應用程式,因為這類應用程式會啟用向使用者顯示的精細權限同意畫面。在這種情況下,請確保程式碼能處理使用者僅授權部分範圍的情形。

如何判斷應用程式是否使用多個範圍

檢查應用程式程式碼或傳出的網路呼叫,判斷應用程式發出的 Google OAuth 2.0 授權要求是否會觸發精細權限同意畫面。

檢查應用程式程式碼

請檢查應用程式程式碼中,呼叫 Google OAuth 授權端點向使用者要求權限的部分。如果您使用其中一個 Google API 用戶端程式庫,通常可以在用戶端初始化步驟中,找到應用程式要求的範圍。下一節會提供一些範例。請參閱應用程式用於處理 Google OAuth 2.0 的 SDK 說明文件,判斷應用程式是否受到影響,並參考下列範例中的指引。

Google Identity Services

下列 Google Identity 服務 JavaScript 程式庫程式碼片段會使用多個非登入範圍初始化 TokenClient。當網頁應用程式要求使用者授權時,系統會顯示精細權限同意畫面。

const 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: (response) => {
    ...
  },
});

Python

下列程式碼片段使用 google-auth-oauthlib.flow 模組建構授權要求;scope 參數包含兩個非登入範圍。當網頁應用程式向使用者要求授權時,系統會顯示精細權限同意畫面。

import google.oauth2.credentials
import google_auth_oauthlib.flow

# Use the client_secret.json file to identify the application requesting
# authorization. The client ID (from that file) and access scopes are
# required.
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=[
        'https://www.googleapis.com/auth/calendar.readonly',
        'https://www.googleapis.com/auth/contacts.readonly'
    ])

Node.js

下列程式碼片段會建立 google.auth.OAuth2 物件,定義授權要求中的參數,其 scope 參數包含兩個非登入範圍。當網頁應用程式要求使用者授權時,系統會顯示精細權限同意畫面。

const {google} = require('googleapis');

/**
  * To use OAuth2 authentication, we need access to a CLIENT_ID,
  * CLIENT_SECRET, AND REDIRECT_URI from the client_secret.json file. To get
  * these credentials for your application, visit
  * https://console.cloud.google.com/apis/credentials.
  */
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for read-only Calendar and Contacts.
const scopes = [
  'https://www.googleapis.com/auth/calendar.readonly',
  'https://www.googleapis.com/auth/contacts.readonly']
];

// Generate a url that asks permissions
const authorizationUrl = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  /** Pass in the scopes array defined earlier.
    * Alternatively, if only one scope is needed, you can pass a scope URL
    * as a string */
  scope: scopes,
  // Enable incremental authorization. Recommended as best practices.
  include_granted_scopes: true
});

檢查傳出網路呼叫

檢查網路呼叫的方法會因應用程式類型而異。

檢查網路呼叫時,請找出傳送至 Google OAuth 授權端點的要求,並檢查 scope 參數。

這些值會導致系統顯示精細權限同意畫面。

  • scope 參數包含登入範圍和非登入範圍。

    以下範例要求包含所有三個登入範圍和一個非登入範圍,用於查看使用者 Google 雲端硬碟檔案的中繼資料:

    https://accounts.google.com/o/oauth2/v2/auth?
    access_type=offline&
    scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile%20openid%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly&
    include_granted_scopes=true&
    response_type=code&
    redirect_uri=YOUR_REDIRECT_URL&
    client_id=YOUR_CLIENT_ID
  • scope 參數包含多個非登入範圍。

    以下範例要求包含兩個非登入範圍,可查看使用者的 Google 雲端硬碟中繼資料,以及管理特定 Google 雲端硬碟檔案:

    https://accounts.google.com/o/oauth2/v2/auth?
    access_type=offline&
    scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.metadata.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file&
    include_granted_scopes=true&
    response_type=code&
    redirect_uri=YOUR_REDIRECT_URL&
    client_id=YOUR_CLIENT_ID

處理精細權限的最佳做法

如果您判斷應用程式需要更新,才能處理精細權限,請務必更新程式碼,妥善處理多個範圍的同意聲明。所有應用程式都應遵循下列最佳做法:

  1. 詳閱《Google API 服務:使用者資料政策》,並確保您遵守相關規定。
  2. 要求工作所需的特定範圍。您必須遵守 Google OAuth 2.0 政策,只要求必要的範圍。除非應用程式的核心功能需要,否則請避免在登入時要求多個範圍。如果將多個範圍綁在一起,尤其是初次使用者不熟悉應用程式功能時,可能會難以理解為何需要這些權限。這可能會引起警惕,並阻止使用者進一步與您的應用程式互動。
  3. 在提出授權要求前,請向使用者提供理由。清楚說明應用程式為何需要要求的權限、您會如何處理使用者資料,以及使用者核准要求後可獲得哪些好處。我們的研究指出,這些說明可提高使用者信任度和參與度。
  4. 使用 漸進式授權:每當應用程式要求範圍時,請使用漸進式授權,避免管理多個存取權杖。
  5. 查看使用者授予的範圍。一次要求多個範圍時,使用者可能不會授予應用程式要求的所有範圍。應用程式應一律檢查使用者授予的範圍,並停用相關功能,處理任何拒絕範圍。請遵循 Google OAuth 2.0 政策,處理多個範圍的同意聲明,並在使用者明確表示有意使用需要該範圍的特定功能時,才再次提示使用者同意。

更新應用程式以處理精細權限

Android 應用程式

請參閱 SDK 說明文件,瞭解如何與 Google OAuth 2.0 互動,並根據最佳做法更新應用程式,以便處理精細權限。

如果您使用 Play 服務的 auth.api.signin SDK 與 Google OAuth 2.0 互動,可以使用 requestPermissions 函式要求所需的最少範圍,並使用 hasPermissions 函式檢查使用者在要求精細權限時授予的範圍。

Chrome 擴充功能應用程式

請根據最佳做法,使用 Chrome Identity API 搭配 Google OAuth 2.0。

以下範例說明如何正確處理精細權限。

manifest.json

範例資訊清單檔案為 Chrome 擴充功能應用程式宣告兩個非登入範圍。

{
  "name": "Example Chrome extension application",
  ...
  "permissions": [
      "identity"
    ],
  "oauth2" : {
      "client_id": "YOUR_CLIENT_ID",
      "scopes":[
        "https://www.googleapis.com/auth/calendar.readonly",
        "https://www.googleapis.com/auth/contacts.readonly"
      ]
  }
}

不正確的做法

非有即無

使用者點選按鈕,即可啟動授權程序。程式碼片段假設使用者會看到「全有或全無」的同意畫面,其中包含 manifest.json 檔案中指定的兩個範圍。忽略檢查使用者授予的範圍。

oauth.js

...
document.querySelector('button').addEventListener('click', function () {
  chrome.identity.getAuthToken({ interactive: true },
      function (token) {
          if (token === undefined) {
            // User didn't authorize both scopes.
            // Updating the UX and application accordingly
            ...
          } else {
            // User authorized both or one of the scopes.
            // It neglects to check which scopes users granted and assumes
            // users granted all scopes.

            // Calling the APIs, etc.
            ...
          }
      });
});

正確做法

最小範圍

選取所需的最小範圍組合

應用程式應只要求最小範圍。建議您在應用程式需要完成工作時,一次要求一個範圍。

在本範例中,我們假設 manifest.json 檔案中宣告的兩個範圍都是所需範圍的最小集合。oauth.js 檔案會使用 Chrome Identity API 向 Google 啟動授權程序。建議您選擇加入啟用精細權限,讓使用者能進一步控管授予應用程式的權限。應用程式應檢查使用者授權的範圍,妥善處理使用者的回應。

oauth.js

...
document.querySelector('button').addEventListener('click', function () {
  chrome.identity.getAuthToken(
      { interactive: true, enableGranularPermissions: true },
      function (token, grantedScopes) {
          if (token === undefined) {
            // User didn't authorize any scope.
            // Updating the UX and application accordingly
            ...
          } else {
            // User authorized the request. Now, check which scopes were
            // granted.
            if (grantedScopes.includes(
                'https://www.googleapis.com/auth/calendar.readonly'))
            {
              // User authorized Calendar read permission.
              // Calling the APIs, etc.
              ...
            }
            else
            {
              // User didn't authorize Calendar read permission.
              // Update UX and application accordingly
              ...
            }

            if (grantedScopes.includes(
                'https://www.googleapis.com/auth/contacts.readonly'))
            {
              // User authorized Contacts read permission.
              // Calling the APIs, etc.
              ...
            }
            else
            {
              // User didn't authorize Contacts read permission.
              // Update UX and application accordingly
              ...
            }
          }
      });
});

iOS、iPadOS 和 macOS 應用程式

請參閱 SDK 說明文件,瞭解如何與 Google OAuth 2.0 互動,並根據最佳做法更新應用程式,以便處理精細權限。

如果您使用 Google 登入 for iOS and macOS 程式庫與 Google OAuth 2.0 互動,請參閱說明文件,瞭解如何處理精細權限。

網頁應用程式

請參閱 SDK 說明文件,瞭解如何與 Google OAuth 2.0 互動,並根據最佳做法更新應用程式,以便處理精細權限。

伺服器端 (離線) 存取權

如要使用伺服器端 (離線) 存取模式,請完成下列步驟:

  • 建立伺服器並定義可公開存取的端點,以便接收授權碼。
  • 在 Google Cloud 控制台的「Clients」(用戶端) 頁面中,設定公開端點的重新導向 URI。

下列 NodeJS 程式碼片段範例要求兩個非登入範圍。使用者會看到精細權限同意畫面。

不正確的做法

非有即無

使用者會重新導向至授權網址。程式碼片段假設使用者會看到「全有或全無」的同意畫面,其中包含 scopes 陣列中指定的兩個範圍。未檢查使用者授予的範圍。

main.js

...
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for two non-Sign-In scopes - Google Calendar and Contacts
const scopes = [
  'https://www.googleapis.com/auth/contacts.readonly',
  'https://www.googleapis.com/auth/calendar.readonly'
];

// Generate a url that asks permissions for the Google Calendar and Contacts
// scopes
const authorizationUrl = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  // Pass in the scopes array defined earlier
  scope: scopes,
  // Enable incremental authorization. Recommended as best practices.
  include_granted_scopes: true
});

async function main() {
  const server = http.createServer(async function (req, res) {
    // Example on redirecting user to the Google authorization server.
    if (req.url == '/') {
      res.writeHead(301, { "Location": authorizationUrl });
    }
    // Receive the callback from the Google authorization server.
    if (req.url.startsWith('/oauth2callback')) {
      // Handle the Google authorization server response
      let q = url.parse(req.url, true).query;

      if (q.error) {
        // User didn't authorize both scopes.
        // Updating the UX and application accordingly
        ...
      } else {
        // User authorized both or one of the scopes.
        // It neglects to check which scopes users granted and assumes users
        // granted all scopes.

        // Get access and refresh tokens (if access_type is offline)
        let { tokens } = await oauth2Client.getToken(q.code);
        // Calling the APIs, etc.
        ...
      }
    }
    res.end();
  }).listen(80);
}
正確做法

最小範圍

選取所需的最小範圍組合

應用程式應只要求最小範圍。建議您在應用程式需要完成工作時,一次要求一個範圍。每當應用程式要求範圍時,應使用漸進式授權,避免管理多個存取權杖。

如果應用程式必須要求多個非登入範圍,請一律使用漸進式授權提出要求,並檢查使用者授予的範圍。

在這個範例中,我們假設應用程式需要上述兩個範圍才能正常運作。建議您選擇加入啟用精細權限,讓使用者能進一步控管授予應用程式的權限。應用程式應檢查使用者授權的範圍,妥善處理使用者的回應。

main.js

...
const oauth2Client = new google.auth.OAuth2(
  YOUR_CLIENT_ID,
  YOUR_CLIENT_SECRET,
  YOUR_REDIRECT_URL
);

// Access scopes for two non-Sign-In scopes - Google Calendar and Contacts
const scopes = [
  'https://www.googleapis.com/auth/contacts.readonly',
  'https://www.googleapis.com/auth/calendar.readonly'
];

// Generate a url that asks permissions for the Google Calendar and Contacts
// scopes
const authorizationUrl = oauth2Client.generateAuthUrl({
  // 'online' (default) or 'offline' (gets refresh_token)
  access_type: 'offline',
  // Pass in the scopes array defined earlier
  scope: scopes,
  // Enable incremental authorization. Recommended as best practices.
  include_granted_scopes: true,
  // Set to true to enable more granular permissions for Google OAuth 2.0
  // client IDs created before 2019. No effect for newer Google OAuth 2.0
  // client IDs, since more granular permissions is always enabled for them.
  enable_granular_consent: true
});

async function main() {
  const server = http.createServer(async function (req, res) {
    // Redirect users to the Google authorization server.
    if (req.url == '/') {
      res.writeHead(301, { "Location": authorizationUrl });
    }
    // Receive the callback from the Google authorization server.
    if (req.url.startsWith('/oauth2callback')) {
      // Handle the Google authorization server response
      let q = url.parse(req.url, true).query;

      if (q.error) {
        // User didn't authorize both scopes.
        // Updating the UX and application accordingly
        ...
      } else {
        // Get access and refresh tokens (if access_type is offline)
        let { tokens } = await oauth2Client.getToken(q.code);
        oauth2Client.setCredentials(tokens);

        // User authorized the request. Now, check which scopes were
        // granted.
        if (tokens.scope.includes(
            'https://www.googleapis.com/auth/calendar.readonly'))
        {
          // User authorized Calendar read permission.
          // Calling the APIs, etc.
          ...
        }
        else
        {
          // User didn't authorize Calendar read permission.
          // Calling the APIs, etc.
          ...
        }

        // Check which scopes user granted the permission to application
        if (tokens.scope.includes(
            'https://www.googleapis.com/auth/contacts.readonly'))
        {
          // User authorized Contacts read permission.
          // Calling the APIs, etc.
          ...
        }
        else
        {
          // User didn't authorize Contacts read permission.
          // Update UX and application accordingly
          ...
        }
      }
    }
    res.end();
  }).listen(80);
}

請參閱伺服器端網頁應用程式指南,瞭解如何從伺服器端應用程式存取 Google API。

僅限用戶端存取

  • 如果應用程式使用 Google Identity 服務 JavaScript 程式庫與 Google OAuth 2.0 互動,請參閱這份說明文件,瞭解如何處理精細權限。
  • 如果應用程式使用 JavaScript 直接呼叫 Google OAuth 2.0 授權端點,請參閱這份說明文件,瞭解如何處理精細權限。

測試更新後的應用程式,確認是否能處理精細權限

  1. 列出使用者回應權限要求的所有情況,以及應用程式的預期行為。舉例來說,如果使用者只授權三個要求範圍中的兩個,應用程式應據此運作。
  2. 啟用精細權限並測試應用程式。啟用精細權限的方式有兩種:

    1. 查看應用程式的 OAuth 2.0 同意畫面,確認是否已啟用精細權限。您也可以透過 Google Cloud 控制台,建立新的網頁、Android 或 iOS Google OAuth 2.0 用戶端 ID 進行測試,因為這些 ID 一律會啟用精細權限。
    2. 呼叫 Google OAuth 授權端點時,請將 enable_granular_consent 參數設為 true。部分 SDK 明確支援這項參數。如果是其他平台,請參閱說明文件,瞭解如何手動新增這項參數和其值。如果您的實作方式不支援新增參數,可以透過 Google Cloud 控制台建立新的網頁、Android 或 iOS Google OAuth 2.0 用戶端 ID,僅用於測試目的,如前一點所述。

  3. 測試更新後的應用程式時,請使用個人 Google 帳戶 (@gmail.com),而非 Workspace 帳戶。這是因為具有全網域授權委派或標示為「信任」的 Workspace Enterprise 應用程式,目前不受精細權限異動影響。因此,使用貴機構的 Workspace 帳戶進行測試時,可能不會如預期顯示新的精細同意畫面。