為網路應用程式應用程式使用 OAuth 2.0

本文說明網路伺服器應用程式如何使用 Google API 用戶端程式庫或 Google OAuth 2.0 端點,以實作 OAuth 2.0 授權以存取 Google API。

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

這個 OAuth 2.0 流程是專為使用者授權所設計。適用於可儲存機密資訊及維持狀態的應用程式。經過正確授權的網路伺服器應用程式可在使用者與應用程式互動,或在使用者離開應用程式後存取 API。

網路伺服器應用程式常使用 服務帳戶來授權 API 要求,尤其是在呼叫 Cloud API 存取專案型資料時,而非針對使用者的資料。網路伺服器應用程式可以搭配使用者授權使用服務帳戶。

用戶端程式庫

本頁的語言專屬範例使用 Google API 用戶端程式庫實作 OAuth 2.0 授權。如要執行程式碼範例,您必須先安裝自己語言的用戶端程式庫。

當您使用 Google API 用戶端程式庫處理應用程式的 OAuth 2.0 流程時,用戶端程式庫會執行許多應用程式需要自行處理的動作。例如,它決定應用程式何時可以使用或重新整理已儲存的存取權杖,以及應用程式何時必須取得同意。另外,用戶端程式庫也會產生正確的重新導向網址,並協助實作重新導向處理常式,為存取權杖交換授權碼。

伺服器端應用程式的 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. 填寫表單,然後按一下 [建立]。如果應用程式使用 PHP、Java、Python、Ruby 和 .NET 等語言和架構,就必須指定已獲授權的重新導向 URI。重新導向 URI 是 OAuth 2.0 伺服器可傳送回應的端點。這些端點必須遵守 Google 的驗證規則

    如要進行測試,您可以指定參照本機電腦的 URI,例如 http://localhost:8080。請注意,本文中的所有範例均使用 http://localhost:8080 做為重新導向 URI。

    建議您設計應用程式的驗證端點,讓應用程式不要向網頁上的其他資源公開授權碼。

建立憑證之後,請從 API Console下載 client_secret.json 檔案。將檔案安全地存放在僅限應用程式存取的位置。

識別存取權範圍

範圍可讓應用程式僅要求所需資源的存取權,同時讓使用者控制對應用程式授予的存取權數量。因此,要求的範圍數可能會獲得使用者同意的可能性。

開始實作 OAuth 2.0 授權之前,建議您先找出應用程式需要存取權的範圍。

此外,建議您讓應用程式透過漸進式授權程序要求存取授權範圍,該程序會在您的應用程式中要求存取使用者資料。這個最佳做法可讓使用者更容易瞭解應用程式需要其要求的原因。

OAuth 2.0 API 範圍文件包含可用於存取 Google API 的完整範圍清單。

語言相關規定

如要執行本文件中的任何程式碼範例,您必須具備 Google 帳戶、網際網路和網路瀏覽器。如果您使用的是其中一個 API 用戶端程式庫,請一併參閱下列語言的專屬規定。

PHP

如要執行本文件中的 PHP 程式碼範例,您必須符合以下條件:

  • 已安裝指令列介面 (CLI) 和 JSON 擴充功能的 PHP 5.6 以上版本。
  • Composer 依附元件管理工具。
  • 適用於 PHP 的 Google API 用戶端程式庫:

    composer require google/apiclient:^2.10

Python

如要執行本文件中的 Python 程式碼範例,您需要:

  • Python 2.6 以上版本
  • pip 套件管理工具。
  • 適用於 Python 的 Google API 用戶端程式庫:
    pip install --upgrade google-api-python-client
  • 使用者授權的 google-authgoogle-auth-oauthlibgoogle-auth-httplib2
    pip install --upgrade google-auth google-auth-oauthlib google-auth-httplib2
  • Flask Python 網路應用程式架構。
    pip install --upgrade flask
  • requests HTTP 程式庫。
    pip install --upgrade requests

Ruby

如要執行本文件中的 Ruby 程式碼範例,您需要:

  • Ruby 2.2.2 以上版本
  • Ruby 專用的 Google API 用戶端程式庫:

    gem install google-api-client
  • Sinatra Ruby 網路應用程式架構。

    gem install sinatra

Node.js

如要執行本文件中的 Node.js 程式碼範例,您需要:

  • 維護 LTS、有效 LTS 或現行版本的 Node.js。
  • Google API Node.js 用戶端:

    npm install googleapis

HTTP/REST

您可以直接安裝 OAuth 2.0 端點,不需要安裝任何程式庫。

取得 OAuth 2.0 存取憑證

下列步驟說明您的應用程式如何與 Google 的 OAuth 2.0 伺服器互動,藉此取得使用者同意,以他們的名義執行 API 要求。您的應用程式必須取得同意聲明,才能執行 Google API 要求,並要求使用者提供授權。

以下清單概述了這些步驟:

  1. 您的應用程式會識別所需的權限。
  2. 您的應用程式會將使用者連同所需權限清單重新導向至 Google。
  3. 使用者決定是否授予應用程式權限。
  4. 您的應用程式會判斷使用者做出的決定。
  5. 如果使用者已授予要求的權限,應用程式就會擷取代表使用者發出 API 要求所需的憑證。

步驟 1:設定授權參數

第一步是建立授權要求。該要求會設定可識別應用程式的參數,並定義要要求使用者授予應用程式的權限。

  • 如果您使用 Google 用戶端程式庫進行 OAuth 2.0 驗證和授權,就必須建立及設定用於定義這些參數的物件。
  • 如果您直接呼叫 Google OAuth 2.0 端點,您將會產生一個網址,並設定該網址上的參數。

下方分頁定義網路伺服器應用程式支援的授權參數。語言相關範例也會示範如何使用用戶端程式庫或授權程式庫來設定設定這些參數的物件。

PHP

下方的程式碼片段會建立 Google\Client() 物件,該物件定義授權要求中的參數。

這個物件會使用您 client_secret.json 檔案中的資訊識別出您的應用程式。(如要進一步瞭解該檔案,請參閱建立授權憑證)。這個物件也會指出應用程式要求存取的權限範圍,以及應用程式驗證端點的網址,該網址會處理來自 Google OAuth 2.0 伺服器的回應。最後,程式碼會設定選用的 access_typeinclude_granted_scopes 參數。

例如,以下程式碼會要求使用者存取 Google 雲端硬碟的唯讀離線存取功能:

$client = new Google\Client();
$client->setAuthConfig('client_secret.json');
$client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY);
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php');
// offline access will give you both an access and refresh token so that
// your app can refresh the access token without user interaction.
$client->setAccessType('offline');
// Using "consent" ensures that your application always receives a refresh token.
// If you are not using offline access, you can omit this.
$client->setApprovalPrompt('consent');
$client->setIncludeGrantedScopes(true);   // incremental auth

要求會指定下列資訊:

參數
client_id 必要

應用程式的用戶端 ID。您可以在 API Console Credentials page中找到這個值。

在 PHP 中,呼叫 setAuthConfig 函式,從 client_secret.json 檔案載入授權憑證。

$client = new Google\Client();
$client->setAuthConfig('client_secret.json');
redirect_uri 必要

決定 API 伺服器在使用者完成授權流程後將使用者重新導向的位置。這個值必須與您在用戶端的 API Console Credentials page中設定的 OAuth 2.0 用戶端授權重新導向 URI 完全相符。如果這個值與所提供的 client_id 的授權重新導向 URI 不符,您會收到 redirect_uri_mismatch 錯誤。

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

如要在 PHP 中設定這個值,請呼叫 setRedirectUri 函式。請注意,您必須為指定的 client_id 指定有效的重新導向 URI。

$client->setRedirectUri('https://oauth2.example.com/code');
scope 必要

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

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

如要在 PHP 中設定這個值,請呼叫 addScope 函式:

$client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY);

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

access_type 建議使用

指出您的應用程式是否在使用者瀏覽器中不重新整理存取憑證。有效參數值為 online,也就是預設值和 offline

如果您的應用程式在使用者不在瀏覽器中,需要重新整理存取憑證,請將值設為 offline。這是重新整理本文件中說明存取憑證的方法。這個值會指示 Google 授權伺服器第一次在應用程式交換憑證的權杖時,傳回重新整理權杖「和」存取權杖。

如要在 PHP 中設定這個值,請呼叫 setAccessType 函式:

$client->setAccessType('offline');
state 建議使用

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

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

如要在 PHP 中設定這個值,請呼叫 setState 函式:

$client->setState($sample_passthrough_value);
include_granted_scopes 選填

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

如要在 PHP 中設定這個值,請呼叫 setIncludeGrantedScopes 函式:

$client->setIncludeGrantedScopes(true);
login_hint 選填

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

將參數值設為電子郵件地址或 sub ID,相當於使用者的 Google ID。

如要在 PHP 中設定這個值,請呼叫 setLoginHint 函式:

$client->setLoginHint('None');
prompt 選填

以空格分隔且區分大小寫的提示清單,以向使用者顯示。如未指定這個參數,使用者會在專案首次要求存取權時提示使用者。詳情請參閱「 提示重新同意聲明」一文。

如要在 PHP 中設定這個值,請呼叫 setApprovalPrompt 函式:

$client->setApprovalPrompt('consent');

可能的值為:

none 不顯示任何驗證或同意畫面。不得指定其他值。
consent 提示使用者提供同意聲明。
select_account 提示使用者選取帳戶。

Python

下列程式碼片段使用 google-auth-oauthlib.flow 模組建構授權要求。

程式碼會建構 Flow 物件,以使用建立授權憑證後下載的 client_secret.json 檔案識別應用程式。此外,這個物件也會識別應用程式要求存取的範圍,以及應用程式驗證端點的網址,該網址會處理來自 Google OAuth 2.0 伺服器的回應。最後,程式碼會設定選用的 access_typeinclude_granted_scopes 參數。

例如,以下程式碼會要求使用者存取 Google 雲端硬碟的唯讀離線存取功能:

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/drive.metadata.readonly'])

# Indicate where the API server will redirect the user after the user completes
# the authorization flow. The redirect URI is required. The value must exactly
# match one of the authorized redirect URIs for the OAuth 2.0 client, which you
# configured in the API Console. If this value doesn't match an authorized URI,
# you will get a 'redirect_uri_mismatch' error.
flow.redirect_uri = 'https://www.example.com/oauth2callback'

# Generate URL for request to Google's OAuth 2.0 server.
# Use kwargs to set optional request parameters.
authorization_url, state = flow.authorization_url(
    # Enable offline access so that you can refresh an access token without
    # re-prompting the user for permission. Recommended for web server apps.
    access_type='offline',
    # Enable incremental authorization. Recommended as a best practice.
    include_granted_scopes='true')

要求會指定下列資訊:

參數
client_id 必要

應用程式的用戶端 ID。您可以在 API Console Credentials page中找到這個值。

在 Python 中,呼叫 from_client_secrets_file 方法,從 client_secret.json 檔案擷取用戶端 ID。您也可以使用 from_client_config 方法,該方法會傳送用戶端設定 (原程式碼顯示在用戶端密鑰檔案中,但不會存取檔案本身)。

flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'])
redirect_uri 必要

決定 API 伺服器在使用者完成授權流程後將使用者重新導向的位置。這個值必須與您在用戶端的 API Console Credentials page中設定的 OAuth 2.0 用戶端授權重新導向 URI 完全相符。如果這個值與所提供的 client_id 的授權重新導向 URI 不符,您會收到 redirect_uri_mismatch 錯誤。

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

如要在 Python 中設定這個值,請設定 flow 物件的 redirect_uri 屬性:

flow.redirect_uri = 'https://oauth2.example.com/code'
scope 必要

範圍清單,指出應用程式可代表使用者存取的資源。這些值會告知 Google 向使用者顯示的同意畫面。

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

使用 Python 時,請使用您用於設定 client_id 來指定範圍清單的方法。

flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'])

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

access_type 建議使用

指出您的應用程式是否在使用者瀏覽器中不重新整理存取憑證。有效參數值為 online,也就是預設值和 offline

如果您的應用程式在使用者不在瀏覽器中,需要重新整理存取憑證,請將值設為 offline。這是重新整理本文件中說明存取憑證的方法。這個值會指示 Google 授權伺服器第一次在應用程式交換憑證的權杖時,傳回重新整理權杖「和」存取權杖。

在 Python 中,在呼叫 flow.authorization_url 方法時,指定 access_type 做為關鍵字引數,即可設定 access_type 參數:

authorization_url, state = flow.authorization_url(
    access_type='offline',
    include_granted_scopes='true')
state 建議使用

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

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

在 Python 中,在呼叫 flow.authorization_url 方法時,將 state 指定為關鍵字引數,即可設定 state 參數:

authorization_url, state = flow.authorization_url(
    access_type='offline',
    state=sample_passthrough_value,
    include_granted_scopes='true')
include_granted_scopes 選填

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

在 Python 中,在呼叫 flow.authorization_url 方法時,指定 include_granted_scopes 做為關鍵字引數,即可設定 include_granted_scopes 參數:

authorization_url, state = flow.authorization_url(
    access_type='offline',
    include_granted_scopes='true')
login_hint 選填

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

將參數值設為電子郵件地址或 sub ID,相當於使用者的 Google ID。

在 Python 中,在呼叫 flow.authorization_url 方法時,指定 login_hint 做為關鍵字引數,即可設定 login_hint 參數:

authorization_url, state = flow.authorization_url(
    access_type='offline',
    login_hint='None',
    include_granted_scopes='true')
prompt 選填

以空格分隔且區分大小寫的提示清單,以向使用者顯示。如未指定這個參數,使用者會在專案首次要求存取權時提示使用者。詳情請參閱「 提示重新同意聲明」一文。

在 Python 中,在呼叫 flow.authorization_url 方法時,將 prompt 指定為關鍵字引數,即可設定 prompt 參數:

authorization_url, state = flow.authorization_url(
      access_type='offline',
      prompt='consent',
      include_granted_scopes='true')

可能的值為:

none 不顯示任何驗證或同意畫面。不得指定其他值。
consent 提示使用者提供同意聲明。
select_account 提示使用者選取帳戶。

Ruby

使用您建立的 Client_secrets.json 檔案來設定應用程式中的用戶端物件。設定用戶端物件時,您必須指定應用程式需要存取的範圍,以及應用程式驗證端點的網址,此網址會處理來自 OAuth 2.0 伺服器的回應。

例如,以下程式碼會要求使用者存取 Google 雲端硬碟的唯讀離線存取功能:

require 'google/apis/drive_v2'
require 'google/api_client/client_secrets'

client_secrets = Google::APIClient::ClientSecrets.load
auth_client = client_secrets.to_authorization
auth_client.update!(
  :scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
  :redirect_uri => 'http://www.example.com/oauth2callback',
  :additional_parameters => {
    "access_type" => "offline",         # offline access
    "include_granted_scopes" => "true"  # incremental auth
  }
)

您的應用程式會使用用戶端物件執行 OAuth 2.0 作業,例如產生授權要求網址,以及將存取權杖套用至 HTTP 要求。

Node.js

下方的程式碼片段會建立 google.auth.OAuth2 物件,該物件定義授權要求中的參數。

該物件使用來自 client_secret.json 檔案的資訊,以識別您的應用程式。 如要要求使用者擷取存取權杖,請將使用者重新導向至同意聲明頁面。 如何建立同意聲明頁面網址:

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 Drive activity.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly'
];

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

重要注意事項 - 系統只會在第一次授權時傳回 refresh_token。詳情請參閱 這裡的說明

HTTP/REST

Google 的 OAuth 2.0 端點位於 https://accounts.google.com/o/oauth2/v2/auth。此端點只能透過 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 必要

決定 Google OAuth 2.0 端點是否傳回授權碼。

將網路伺服器應用程式的參數值設為 code

scope 必要

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

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

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

access_type 建議使用

指出您的應用程式是否在使用者瀏覽器中不重新整理存取憑證。有效參數值為 online,也就是預設值和 offline

如果您的應用程式在使用者不在瀏覽器中,需要重新整理存取憑證,請將值設為 offline。這是重新整理本文件中說明存取憑證的方法。這個值會指示 Google 授權伺服器第一次在應用程式交換憑證的權杖時,傳回重新整理權杖「和」存取權杖。

state 建議使用

指定應用程式用來維持授權要求與授權伺服器回應之間狀態的任何字串值。在使用者同意或拒絕應用程式存取要求之後,伺服器會傳回您在 redirect_uri 網址查詢元件 (?) 中,以 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 提示使用者選取帳戶。

步驟 2:重新導向至 Google 的 OAuth 2.0 伺服器

將使用者重新導向 Google 的 OAuth 2.0 伺服器,以啟動驗證和授權程序。此情況通常是應用程式首次需要存取使用者的資料。如果是漸進式授權,當應用程式首次需要存取尚未獲得存取權的其他資源時,也會執行這個步驟。

PHP

  1. 產生網址,以要求存取 Google 的 OAuth 2.0 伺服器:
    $auth_url = $client->createAuthUrl();
  2. 將使用者重新導向 $auth_url
    header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));

Python

以下範例說明如何使用 Flask 網路應用程式架構,將使用者重新導向至授權網址:

return flask.redirect(authorization_url)

Ruby

  1. 產生網址,以要求存取 Google 的 OAuth 2.0 伺服器:
    auth_uri = auth_client.authorization_uri.to_s
  2. 將使用者重新導向 auth_uri

Node.js

  1. 使用步驟 1 generateAuthUrl 方法產生的網址 authorizationUrl,向 Google 的 OAuth 2.0 伺服器要求存取權。
  2. 將使用者重新導向 authorizationUrl
    res.writeHead(301, { "Location": authorizationUrl });

HTTP/REST

Sample redirect to Google's authorization server

An example URL is shown below, with line breaks and spaces for readability.

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly&
 access_type=offline&
 include_granted_scopes=true&
 response_type=code&
 state=state_parameter_passthrough_value&
 redirect_uri=https%3A//oauth2.example.com/code&
 client_id=client_id

建立要求網址之後,請將使用者重新導向至該網址。

Google 的 OAuth 2.0 伺服器會驗證使用者,並取得使用者同意,允許應用程式存取您要求的範圍。系統會使用您指定的重新導向網址,將回應傳回至應用程式。

步驟 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

OAuth 用戶端密鑰不正確。查看 OAuth 用戶端設定,包括用於此要求的用戶端 ID 和密鑰。

invalid_grant

重新整理存取憑證或使用漸進式授權時,憑證可能已過期或失效。重新驗證使用者,並要求使用者同意取得新權杖。如果繼續看到這則錯誤訊息,請確認您的應用程式已正確設定,且您在要求中使用了正確的憑證和參數。否則,該使用者帳戶可能已遭刪除或停用。

redirect_uri_mismatch

授權要求中傳遞的 redirect_uri 與 OAuth 用戶端 ID 的授權重新導向 URI 不符。查看 Google API Console Credentials page中獲得授權的重新導向 URI。

redirect_uri 參數可能是指已不適用且不再支援的 OAuth 頻外 (OOB) 流程。請參閱遷移指南來更新整合作業。

步驟 4:處理 OAuth 2.0 伺服器回應

OAuth 2.0 伺服器會使用要求中指定的網址來回應應用程式的存取要求。

如果使用者核准存取要求,回應就會包含授權碼。如果使用者未核准要求,回應就會包含錯誤訊息。傳回到網路伺服器的授權碼或錯誤訊息會顯示在查詢字串中,如下所示:

錯誤回應:

https://oauth2.example.com/auth?error=access_denied

授權碼回應:

https://oauth2.example.com/auth?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7

OAuth 2.0 伺服器回應範例

您可以點選下列範例網址來測試此流程,要求存取 Google 雲端硬碟中的檔案中繼資料:

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly&
 access_type=offline&
 include_granted_scopes=true&
 response_type=code&
 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 中傳回的資訊提供更多詳細資訊。

步驟 5:交換更新憑證和存取權杖的授權碼

網路伺服器收到授權碼後,就能將授權碼交換成存取權杖。

PHP

如要交換存取權杖的授權碼,請使用 authenticate 方法:

$client->authenticate($_GET['code']);

您可以使用 getAccessToken 方法擷取存取權杖:

$access_token = $client->getAccessToken();

Python

在回呼頁面中,使用 google-auth 程式庫來驗證授權伺服器回應。接著,請使用 flow.fetch_token 方法交換該回應中的授權碼以取得存取權杖:

state = flask.session['state']
flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
    'client_secret.json',
    scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'],
    state=state)
flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

authorization_response = flask.request.url
flow.fetch_token(authorization_response=authorization_response)

# Store the credentials in the session.
# ACTION ITEM for developers:
#     Store user's access and refresh tokens in your data store if
#     incorporating this code into your real app.
credentials = flow.credentials
flask.session['credentials'] = {
    'token': credentials.token,
    'refresh_token': credentials.refresh_token,
    'token_uri': credentials.token_uri,
    'client_id': credentials.client_id,
    'client_secret': credentials.client_secret,
    'scopes': credentials.scopes}

Ruby

如要交換存取權杖的授權碼,請使用 fetch_access_token! 方法:

auth_client.code = auth_code
auth_client.fetch_access_token!

Node.js

如要交換存取權杖的授權碼,請使用 getToken 方法:

const url = require('url');

// Receive the callback from Google's OAuth 2.0 server.
if (req.url.startsWith('/oauth2callback')) {
  // Handle the OAuth 2.0 server response
  let q = url.parse(req.url, true).query;

  // Get access and refresh tokens (if access_type is offline)
  let { tokens } = await oauth2Client.getToken(q.code);
  oauth2Client.setCredentials(tokens);
}

HTTP/REST

如要交換存取權杖的授權碼,請呼叫 https://oauth2.googleapis.com/token 端點並設定下列參數:

欄位
client_id 從 API Console Credentials page取得的用戶端 ID。
client_secret 從 API Console Credentials page取得的用戶端密鑰。
code 初始要求傳回的授權碼。
grant_type OAuth 2.0 規格所定義,這個欄位的值必須設為 authorization_code
redirect_uri 針對您的專案, API Console Credentials page client_id

下列程式碼片段顯示要求範例:

POST /token HTTP/1.1
Host: oauth2.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https%3A//oauth2.example.com/code&
grant_type=authorization_code

Google 回應此要求後,會傳回含有短期存取權杖和更新權杖的 JSON 物件。 請注意,只有當您的應用程式在向 Google 授權伺服器發出初始要求時,將 access_type 參數設為 offline 時,才會傳回重新整理權杖。

回應中包含以下欄位:

欄位
access_token 應用程式傳送憑證以授權 Google API 要求。
expires_in 存取憑證的剩餘生命週期 (單位為秒)。
refresh_token 可用來取得新的存取權杖的權杖。重新整理權杖的效力將直到使用者撤銷存取權為止。再次提醒您,如果您在初始請求中,將 access_type 參數設為 offline,則這個欄位僅會顯示於 Google 授權伺服器中。
scope access_token 授予的存取權範圍,以空格分隔且區分大小寫的字串清單表示。
token_type 傳回的權杖類型。目前,這個欄位的值一律會設為 Bearer

下列程式碼片段顯示回應範例:

{
  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "token_type": "Bearer",
  "scope": "https://www.googleapis.com/auth/drive.metadata.readonly",
  "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}

錯誤

交換存取權杖的授權碼時,您可能會遇到下列錯誤,而不是預期的回應。以下列出常見的錯誤代碼和建議解決方案。

invalid_grant

提供的授權碼無效或格式有誤。請重新啟動 OAuth 程序來要求使用者提供新的代碼,藉此提示他們再次同意。

呼叫 Google API

PHP

使用存取憑證以呼叫 Google API,請完成下列步驟:

  1. 如果您需要將存取權杖套用至新的 Google\Client 物件 (例如,已將存取權杖儲存在使用者工作階段中),請使用 setAccessToken 方法:
    $client->setAccessToken($access_token);
  2. 為您要呼叫的 API 建構服務物件。您可以為服務呼叫的 API 提供授權的 Google\Client 物件,藉此建構服務物件。比如呼叫 Drive API:
    $drive = new Google\Service\Drive($client);
  3. 使用服務物件提供的介面向 API 服務發出要求。例如,若要在已驗證使用者的 Google 雲端硬碟中列出檔案:
    $files = $drive->files->listFiles(array())->getItems();

Python

取得存取權杖後,您的應用程式就能使用該權杖,以代表指定使用者帳戶或服務帳戶來授權 API 要求。透過使用者專屬的授權憑證,針對您要呼叫的 API 建構服務物件,然後使用該物件提出授權的 API 要求。

  1. 為您要呼叫的 API 建構服務物件。要建立服務物件,方法是呼叫 googleapiclient.discovery 程式庫的 build 方法,並提供 API 名稱、版本和使用者憑證: 例如,呼叫 Drive API 第 2 版:
    from googleapiclient.discovery import build
    
    drive = build('drive', 'v2', credentials=credentials)
  2. 使用服務物件提供的介面向 API 服務發出要求。例如,若要在已驗證使用者的 Google 雲端硬碟中列出檔案:
    files = drive.files().list().execute()

Ruby

完成下列步驟,使用 auth_client 物件呼叫 Google API:

  1. 為您要呼叫的 API 建構服務物件。例如,呼叫 Drive API 第 2 版:
    drive = Google::Apis::DriveV2::DriveService.new
  2. 在服務中設定憑證:
    drive.authorization = auth_client
  3. 使用服務物件提供的介面向 API 服務發出要求。例如,若要在已驗證使用者的 Google 雲端硬碟中列出檔案:
    files = drive.list_files

或者,您也可以為方法提供 options 參數,藉此根據方法提供授權:

files = drive.list_files(options: { authorization: auth_client })

Node.js

取得存取權杖並將其設為 OAuth2 物件後,請使用該物件呼叫 Google API。您的應用程式可利用該憑證,代表指定使用者帳戶或服務帳戶來授權 API 要求。為您要呼叫的 API 建構服務物件。

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

// Example of using Google Drive API to list filenames in user's Drive.
const drive = google.drive('v3');
drive.files.list({
  auth: oauth2Client,
  pageSize: 10,
  fields: 'nextPageToken, files(id, name)',
}, (err1, res1) => {
  if (err1) return console.log('The API returned an error: ' + err1);
  const files = res1.data.files;
  if (files.length) {
    console.log('Files:');
    files.map((file) => {
      console.log(`${file.name} (${file.id})`);
    });
  } else {
    console.log('No files found.');
  }
});

HTTP/REST

應用程式取得存取權杖後,如果 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

完整範例

以下範例會在使用者驗證之後,在使用者的 Google 雲端硬碟中列印 JSON 格式的檔案清單,並授權應用程式存取使用者的雲端硬碟中繼資料。

PHP

如何執行這個範例:

  1. 在 API Console中,將本機電腦的網址加入重新導向網址清單,例如 http://localhost:8080
  2. 建立新目錄並進行變更。例如:
    mkdir ~/php-oauth2-example
    cd ~/php-oauth2-example
  3. 使用 Composer 安裝 PHP 適用的 Google API 用戶端程式庫
    composer require google/apiclient:^2.10
  4. 使用下列內容建立 index.phpoauth2callback.php 檔案。
  5. 使用設定為提供 PHP 的網路伺服器執行範例。如果您使用的是 PHP 5.6 以上版本,可以使用 PHP 內建的測試網路伺服器:
    php -S localhost:8080 ~/php-oauth2-example

index.php

<?php
require_once __DIR__.'/vendor/autoload.php';

session_start();

$client = new Google\Client();
$client->setAuthConfig('client_secrets.json');
$client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY);

if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
  $client->setAccessToken($_SESSION['access_token']);
  $drive = new Google\Service\Drive($client);
  $files = $drive->files->listFiles(array())->getItems();
  echo json_encode($files);
} else {
  $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php';
  header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}

oauth2callback.php

<?php
require_once __DIR__.'/vendor/autoload.php';

session_start();

$client = new Google\Client();
$client->setAuthConfigFile('client_secrets.json');
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php');
$client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY);

if (! isset($_GET['code'])) {
  $auth_url = $client->createAuthUrl();
  header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
  $client->authenticate($_GET['code']);
  $_SESSION['access_token'] = $client->getAccessToken();
  $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/';
  header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}

Python

這個範例使用 Flask 架構。這個資料庫是在 http://localhost:8080 執行網路應用程式,可讓您測試 OAuth 2.0 流程。如果前往該網址,您會看到四個連結:

  • 測試 API 要求:這個連結會指向嘗試執行範例 API 要求的網頁,如有需要,系統會啟動授權流程。如果成功,頁面會顯示 API 回應。
  • 直接測試驗證流程:這個連結會指向嘗試透過授權流程傳送使用者的網頁。應用程式會要求使用者代表使用者提交已獲授權的 API 要求。
  • 撤銷目前的憑證:這個連結會指向使用者 撤銷已授予應用程式權限的網頁。
  • 清除 Flask 工作階段憑證:這個連結會清除儲存在 Flask 工作階段的授權憑證。這可讓您瞭解已授予應用程式權限的使用者,在新工作階段執行 API 要求時會發生什麼情況。此外,如果應用程式已撤銷使用者授予應用程式的權限,您的應用程式也會收到 API 回應,而應用程式仍會嘗試授權已撤銷存取憑證。
# -*- coding: utf-8 -*-

import os
import flask
import requests

import google.oauth2.credentials
import google_auth_oauthlib.flow
import googleapiclient.discovery

# This variable specifies the name of a file that contains the OAuth 2.0
# information for this application, including its client_id and client_secret.
CLIENT_SECRETS_FILE = "client_secret.json"

# This OAuth 2.0 access scope allows for full read/write access to the
# authenticated user's account and requires requests to use an SSL connection.
SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly']
API_SERVICE_NAME = 'drive'
API_VERSION = 'v2'

app = flask.Flask(__name__)
# Note: A secret key is included in the sample so that it works.
# If you use this code in your application, replace this with a truly secret
# key. See https://flask.palletsprojects.com/quickstart/#sessions.
app.secret_key = 'REPLACE ME - this value is here as a placeholder.'


@app.route('/')
def index():
  return print_index_table()


@app.route('/test')
def test_api_request():
  if 'credentials' not in flask.session:
    return flask.redirect('authorize')

  # Load credentials from the session.
  credentials = google.oauth2.credentials.Credentials(
      **flask.session['credentials'])

  drive = googleapiclient.discovery.build(
      API_SERVICE_NAME, API_VERSION, credentials=credentials)

  files = drive.files().list().execute()

  # Save credentials back to session in case access token was refreshed.
  # ACTION ITEM: In a production app, you likely want to save these
  #              credentials in a persistent database instead.
  flask.session['credentials'] = credentials_to_dict(credentials)

  return flask.jsonify(**files)


@app.route('/authorize')
def authorize():
  # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps.
  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      CLIENT_SECRETS_FILE, scopes=SCOPES)

  # The URI created here must exactly match one of the authorized redirect URIs
  # for the OAuth 2.0 client, which you configured in the API Console. If this
  # value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch'
  # error.
  flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

  authorization_url, state = flow.authorization_url(
      # Enable offline access so that you can refresh an access token without
      # re-prompting the user for permission. Recommended for web server apps.
      access_type='offline',
      # Enable incremental authorization. Recommended as a best practice.
      include_granted_scopes='true')

  # Store the state so the callback can verify the auth server response.
  flask.session['state'] = state

  return flask.redirect(authorization_url)


@app.route('/oauth2callback')
def oauth2callback():
  # Specify the state when creating the flow in the callback so that it can
  # verified in the authorization server response.
  state = flask.session['state']

  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
      CLIENT_SECRETS_FILE, scopes=SCOPES, state=state)
  flow.redirect_uri = flask.url_for('oauth2callback', _external=True)

  # Use the authorization server's response to fetch the OAuth 2.0 tokens.
  authorization_response = flask.request.url
  flow.fetch_token(authorization_response=authorization_response)

  # Store credentials in the session.
  # ACTION ITEM: In a production app, you likely want to save these
  #              credentials in a persistent database instead.
  credentials = flow.credentials
  flask.session['credentials'] = credentials_to_dict(credentials)

  return flask.redirect(flask.url_for('test_api_request'))


@app.route('/revoke')
def revoke():
  if 'credentials' not in flask.session:
    return ('You need to <a href="/authorize">authorize</a> before ' +
            'testing the code to revoke credentials.')

  credentials = google.oauth2.credentials.Credentials(
    **flask.session['credentials'])

  revoke = requests.post('https://oauth2.googleapis.com/revoke',
      params={'token': credentials.token},
      headers = {'content-type': 'application/x-www-form-urlencoded'})

  status_code = getattr(revoke, 'status_code')
  if status_code == 200:
    return('Credentials successfully revoked.' + print_index_table())
  else:
    return('An error occurred.' + print_index_table())


@app.route('/clear')
def clear_credentials():
  if 'credentials' in flask.session:
    del flask.session['credentials']
  return ('Credentials have been cleared.<br><br>' +
          print_index_table())


def credentials_to_dict(credentials):
  return {'token': credentials.token,
          'refresh_token': credentials.refresh_token,
          'token_uri': credentials.token_uri,
          'client_id': credentials.client_id,
          'client_secret': credentials.client_secret,
          'scopes': credentials.scopes}

def print_index_table():
  return ('<table>' +
          '<tr><td><a href="/test">Test an API request</a></td>' +
          '<td>Submit an API request and see a formatted JSON response. ' +
          '    Go through the authorization flow if there are no stored ' +
          '    credentials for the user.</td></tr>' +
          '<tr><td><a href="/authorize">Test the auth flow directly</a></td>' +
          '<td>Go directly to the authorization flow. If there are stored ' +
          '    credentials, you still might not be prompted to reauthorize ' +
          '    the application.</td></tr>' +
          '<tr><td><a href="/revoke">Revoke current credentials</a></td>' +
          '<td>Revoke the access token associated with the current user ' +
          '    session. After revoking credentials, if you go to the test ' +
          '    page, you should see an <code>invalid_grant</code> error.' +
          '</td></tr>' +
          '<tr><td><a href="/clear">Clear Flask session credentials</a></td>' +
          '<td>Clear the access token currently stored in the user session. ' +
          '    After clearing the token, if you <a href="/test">test the ' +
          '    API request</a> again, you should go back to the auth flow.' +
          '</td></tr></table>')


if __name__ == '__main__':
  # When running locally, disable OAuthlib's HTTPs verification.
  # ACTION ITEM for developers:
  #     When running in production *do not* leave this option enabled.
  os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1'

  # Specify a hostname and port that are set as a valid redirect URI
  # for your API project in the Google API Console.
  app.run('localhost', 8080, debug=True)

Ruby

這個範例使用 Sinatra 架構。

require 'google/apis/drive_v2'
require 'google/api_client/client_secrets'
require 'json'
require 'sinatra'

enable :sessions
set :session_secret, 'setme'

get '/' do
  unless session.has_key?(:credentials)
    redirect to('/oauth2callback')
  end
  client_opts = JSON.parse(session[:credentials])
  auth_client = Signet::OAuth2::Client.new(client_opts)
  drive = Google::Apis::DriveV2::DriveService.new
  files = drive.list_files(options: { authorization: auth_client })
  "<pre>#{JSON.pretty_generate(files.to_h)}</pre>"
end

get '/oauth2callback' do
  client_secrets = Google::APIClient::ClientSecrets.load
  auth_client = client_secrets.to_authorization
  auth_client.update!(
    :scope => 'https://www.googleapis.com/auth/drive.metadata.readonly',
    :redirect_uri => url('/oauth2callback'))
  if request['code'] == nil
    auth_uri = auth_client.authorization_uri.to_s
    redirect to(auth_uri)
  else
    auth_client.code = request['code']
    auth_client.fetch_access_token!
    auth_client.client_secret = nil
    session[:credentials] = auth_client.to_json
    redirect to('/')
  end
end

Node.js

如何執行這個範例:

  1. 在 API Console中,將本機電腦的網址加入重新導向網址清單,例如新增 http://localhost
  2. 請確認您已安裝維護 LTS、使用中的 LTS 或現行版本的 Node.js。
  3. 建立新目錄並進行變更。例如:
    mkdir ~/nodejs-oauth2-example
    cd ~/nodejs-oauth2-example
  4. Install the Google API Client Library for Node.js using npm:
    npm install googleapis
  5. 使用下列內容建立 main.js 檔案。
  6. 執行範例:
    node .\main.js

main.js

const http = require('http');
const https = require('https');
const url = require('url');
const { google } = require('googleapis');

/**
 * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI.
 * 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 Drive activity.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly'
];

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

/* Global variable that stores user credential in this code example.
 * ACTION ITEM for developers:
 *   Store user's refresh token in your data store if
 *   incorporating this code into your real app.
 *   For more information on handling refresh tokens,
 *   see https://github.com/googleapis/google-api-nodejs-client#handling-refresh-tokens
 */
let userCredential = null;

async function main() {
  const server = http.createServer(async function (req, res) {
    // Example on redirecting user to Google's OAuth 2.0 server.
    if (req.url == '/') {
      res.writeHead(301, { "Location": authorizationUrl });
    }

    // Receive the callback from Google's OAuth 2.0 server.
    if (req.url.startsWith('/oauth2callback')) {
      // Handle the OAuth 2.0 server response
      let q = url.parse(req.url, true).query;

      if (q.error) { // An error response e.g. error=access_denied
        console.log('Error:' + q.error);
      } else { // Get access and refresh tokens (if access_type is offline)
        let { tokens } = await oauth2Client.getToken(q.code);
        oauth2Client.setCredentials(tokens);

        /** Save credential to the global variable in case access token was refreshed.
          * ACTION ITEM: In a production app, you likely want to save the refresh token
          *              in a secure persistent database instead. */
        userCredential = tokens;

        // Example of using Google Drive API to list filenames in user's Drive.
        const drive = google.drive('v3');
        drive.files.list({
          auth: oauth2Client,
          pageSize: 10,
          fields: 'nextPageToken, files(id, name)',
        }, (err1, res1) => {
          if (err1) return console.log('The API returned an error: ' + err1);
          const files = res1.data.files;
          if (files.length) {
            console.log('Files:');
            files.map((file) => {
              console.log(`${file.name} (${file.id})`);
            });
          } else {
            console.log('No files found.');
          }
        });
      }
    }

    // Example on revoking a token
    if (req.url == '/revoke') {
      // Build the string for the POST request
      let postData = "token=" + userCredential.access_token;

      // Options for POST request to Google's OAuth 2.0 server to revoke a token
      let postOptions = {
        host: 'oauth2.googleapis.com',
        port: '443',
        path: '/revoke',
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': Buffer.byteLength(postData)
        }
      };

      // Set up the request
      const postReq = https.request(postOptions, function (res) {
        res.setEncoding('utf8');
        res.on('data', d => {
          console.log('Response: ' + d);
        });
      });

      postReq.on('error', error => {
        console.log(error)
      });

      // Post the request with data
      postReq.write(postData);
      postReq.end();
    }
    res.end();
  }).listen(80);
}
main().catch(console.error);

HTTP/REST

這個 Python 範例使用 Flask 架構與 Requests 程式庫來示範 OAuth 2.0 網路流程。針對這個流程,建議您使用 Python 專用的 Google API 用戶端程式庫。(Python 中的範例確實使用用戶端程式庫)。

import json

import flask
import requests


app = flask.Flask(__name__)

CLIENT_ID = '123456789.apps.googleusercontent.com'
CLIENT_SECRET = 'abc123'  # Read from a file or environmental variable in a real app
SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly'
REDIRECT_URI = 'http://example.com/oauth2callback'


@app.route('/')
def index():
  if 'credentials' not in flask.session:
    return flask.redirect(flask.url_for('oauth2callback'))
  credentials = json.loads(flask.session['credentials'])
  if credentials['expires_in'] <= 0:
    return flask.redirect(flask.url_for('oauth2callback'))
  else:
    headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])}
    req_uri = 'https://www.googleapis.com/drive/v2/files'
    r = requests.get(req_uri, headers=headers)
    return r.text


@app.route('/oauth2callback')
def oauth2callback():
  if 'code' not in flask.request.args:
    auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code'
                '&client_id={}&redirect_uri={}&scope={}').format(CLIENT_ID, REDIRECT_URI, SCOPE)
    return flask.redirect(auth_uri)
  else:
    auth_code = flask.request.args.get('code')
    data = {'code': auth_code,
            'client_id': CLIENT_ID,
            'client_secret': CLIENT_SECRET,
            'redirect_uri': REDIRECT_URI,
            'grant_type': 'authorization_code'}
    r = requests.post('https://oauth2.googleapis.com/token', data=data)
    flask.session['credentials'] = r.text
    return flask.redirect(flask.url_for('index'))


if __name__ == '__main__':
  import uuid
  app.secret_key = str(uuid.uuid4())
  app.debug = False
  app.run()

重新導向 URI 驗證規則

Google 將下列驗證規則套用至 URI,以協助開發人員確保應用程式的安全。您的重新導向 URI 必須遵守這些規則。如需網域、主機、路徑、查詢、配置和使用者資訊的定義,請參閱 RFC 3986 第 3 節

驗證規則
配置

重新導向 URI 必須使用 HTTPS 配置,而非純 HTTP。本機主機 URI (包括 localhost IP 位址 URI) 不受此規則影響。

主機

主機不可以是原始 IP 位址。本機主機 IP 位址排除在這項規則之外。

網域
  • 主機 TLD (頂層網域) 必須屬於公開尾碼清單
  • 主機網域不能為 “googleusercontent.com”
  • 除非應用程式擁有網域,否則重新導向 URI 不得包含網址縮短網域 (例如 goo.gl)。此外,如果擁有較短網域的應用程式選擇重新導向至該網域,則該重新導向 URI 的路徑中必須包含 “/google-callback/”,結尾須為 “/google-callback”
  • 使用者資訊

    重新導向 URI 不得包含 userinfo 子元件。

    路徑

    重新導向 URI 不可包含由 “/..”“\..” 或其網址編碼所代表的路徑週遊,也稱為目錄反向追蹤。

    查詢

    重新導向 URI 不得包含開放式重新導向

    Fragment

    重新導向 URI 不能含有片段元件。

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

    漸進式授權

    在 OAuth 2.0 通訊協定中,您的應用程式會要求存取資源,這類資源以範圍識別。在需要時為資源要求授權時,這是一種最佳使用者體驗。為了執行這種做法,Google 的授權伺服器支援漸進式授權。這項功能可讓您按需求要求範圍,如果使用者授予新範圍的權限,則會傳回授權碼,以交換含有使用者授予專案所有範圍的符記。

    舉例來說,允許使用者在不登入的狀態下,取樣音樂曲目並製作合輯。然而,儲存完成的組合需要存取 Google 雲端硬碟。假如使用者在應用程式實際需要時要求存取 Google 雲端硬碟,那麼大多數人都會覺得自己的網站很自然。

    在這種情況下,應用程式可在登入時要求 openidprofile 範圍執行基本登入,然後在第一個要求時儲存 https://www.googleapis.com/auth/drive.file 範圍以儲存組合。

    如要實作漸進式授權,您必須完成一般要求提出存取權杖的要求,但請確認授權要求已包含先前授予的範圍。這個方法可讓應用程式不必管理多個存取憑證。

    下列規則適用於透過漸進式授權機制取得的存取權杖:

    • 權杖可用於存取與新組合授權結合的任何範圍對應的資源。
    • 使用合併權杖取得合併授權以取得存取權杖時,存取權杖代表合併的授權,並可用於回應中包含的任何 scope 值。
    • 即使已向不同的用戶端要求授權,合併授權也會納入使用者授予 API 專案的所有範圍。舉例來說,如果使用者透過應用程式的桌面用戶端授予某個範圍的存取權,然後透過行動用戶端將同一個範圍授予同一個應用程式,則合併授權將同時包含這兩個範圍。
    • 如果您撤銷代表合併授權的權杖,系統就會代表相關使用者同時撤銷該授權的所有範圍存取權。

    步驟 1:設定授權參數」中的語言專屬程式碼範例,以及步驟 2:重新導向至 Google 的 OAuth 2.0 伺服器中的 HTTP/REST 重新導向網址範例,均使用漸進式授權。下列程式碼範例也會顯示使用漸進式授權所需的程式碼。

    PHP

    $client->setIncludeGrantedScopes(true);

    Python

    在 Python 中,將 include_granted_scopes 關鍵字引數設為 true,以確保授權要求包含先前授予的範圍。include_granted_scopes 很有可能不是您所設定的「唯一」關鍵字引數,如下方範例所示。

    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='true')

    Ruby

    auth_client.update!(
      :additional_parameters => {"include_granted_scopes" => "true"}
    )

    Node.js

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

    HTTP/REST

    GET https://accounts.google.com/o/oauth2/v2/auth?
      client_id=your_client_id&
      response_type=code&
      state=state_parameter_passthrough_value&
      scope=https%3A//www.googleapis.com/auth/drive.file&
      redirect_uri=https%3A//oauth2.example.com/code&
      prompt=consent&
      include_granted_scopes=true

    重新整理存取憑證 (離線存取)

    存取憑證經常失效,且會成為相關 API 要求的無效憑證。如果您要求取得與憑證相關的範圍的離線存取權,可以不先提示使用者授予權限 (包括使用者不存在時)。

    • 如果您使用 Google API 用戶端程式庫,只要已設定物件以離線存取,用戶端物件就會視需要重新整理存取權杖。
    • 如果您不使用用戶端程式庫,就必須將使用者重新導向 Google 的 OAuth 2.0 伺服器時,將 access_type HTTP 查詢參數設為 offline。在這種情況下,當您針對存取權杖交換授權碼時,Google 的授權伺服器會傳回更新權杖。之後,如果存取權杖過期 (或任何其他時間),您就能使用更新權杖來取得新的存取權杖。

    任何需要使用者存取 Google API 的應用程式都必須要求離線存取。例如,如果應用程式在執行時具有備份服務或依預定時間執行動作,就必須在沒有使用者的情況下重新整理其存取權杖。預設存取權樣式稱為 online

    伺服器端網路應用程式、已安裝的應用程式和裝置在授權過程中都會取得更新憑證。重新整理權杖通常不用於用戶端 (JavaScript) 網頁應用程式。

    PHP

    如果應用程式需要離線存取 Google API,請將 API 用戶端的存取權類型設定為 offline

    $client->setAccessType("offline");

    在使用者授予對指定範圍的離線存取權後,您可以繼續透過 API 用戶端,在使用者離線時代表他們存取 Google API。用戶端物件會視需要重新整理存取憑證。

    Python

    使用 Python 時,請將 access_type 關鍵字引數設為 offline,如此一來,您就不必重新提示使用者授予權限,就能重新整理存取憑證。access_type 很有可能不是您「只」設定的關鍵字引數,如下方範例所示。

    authorization_url, state = flow.authorization_url(
        # Enable offline access so that you can refresh an access token without
        # re-prompting the user for permission. Recommended for web server apps.
        access_type='offline',
        # Enable incremental authorization. Recommended as a best practice.
        include_granted_scopes='true')

    在使用者授予對指定範圍的離線存取權後,您可以繼續透過 API 用戶端,在使用者離線時代表他們存取 Google API。用戶端物件會視需要重新整理存取憑證。

    Ruby

    如果應用程式需要離線存取 Google API,請將 API 用戶端的存取權類型設定為 offline

    auth_client.update!(
      :additional_parameters => {"access_type" => "offline"}
    )

    在使用者授予對指定範圍的離線存取權後,您可以繼續透過 API 用戶端,在使用者離線時代表他們存取 Google API。用戶端物件會視需要重新整理存取憑證。

    Node.js

    如果應用程式需要離線存取 Google API,請將 API 用戶端的存取權類型設定為 offline

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

    在使用者授予對指定範圍的離線存取權後,您可以繼續透過 API 用戶端,在使用者離線時代表他們存取 Google API。用戶端物件會視需要重新整理存取憑證。

    存取憑證過期。如果這個憑證即將過期,這個程式庫會自動使用重新整理權杖來取得新的存取權杖。如要確保隨時儲存最新憑證,最簡單的方法是使用憑證事件:

    oauth2Client.on('tokens', (tokens) => {
      if (tokens.refresh_token) {
        // store the refresh_token in your secure persistent database
        console.log(tokens.refresh_token);
      }
      console.log(tokens.access_token);
    });

    這個符記事件只會在第一個授權中發生,您必須在呼叫 generateAuthUrl 方法時將 access_type 設為 offline,才能接收更新權杖。如果您已為應用程式授予所需權限,且沒有設定接收更新權杖的適當限制,就必須重新授權應用程式以取得新的重新整理權杖。

    如要稍後再設定 refresh_token,可以使用 setCredentials 方法:

    oauth2Client.setCredentials({
      refresh_token: `STORED_REFRESH_TOKEN`
    });
    

    當用戶端有更新權杖之後,系統就會自動取得存取權杖,並在下次呼叫 API 時自動重新整理。

    HTTP/REST

    如要重新整理存取憑證,您的應用程式會將 HTTPS POST 要求傳送至 Google 的授權伺服器 (https://oauth2.googleapis.com/token),其中包含下列參數:

    欄位
    client_id API Console取得的用戶端 ID。
    client_secret API Console取得的用戶端密鑰。
    grant_type OAuth 2.0 規格定義,這個欄位的值必須設為 refresh_token
    refresh_token 從授權碼交換所傳回的更新憑證。

    下列程式碼片段顯示要求範例:

    POST /token HTTP/1.1
    Host: oauth2.googleapis.com
    Content-Type: application/x-www-form-urlencoded
    
    client_id=your_client_id&
    client_secret=your_client_secret&
    refresh_token=refresh_token&
    grant_type=refresh_token

    只要使用者尚未撤銷已授予應用程式的存取權,權杖伺服器會傳回含有新存取憑證的 JSON 物件。下列程式碼片段顯示回應範例:

    {
      "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
      "expires_in": 3920,
      "scope": "https://www.googleapis.com/auth/drive.metadata.readonly",
      "token_type": "Bearer"
    }

    請注意,要核發的更新憑證有數量限制;每個用戶端/使用者組合各有一項限制,且所有用戶端對每位使用者都各有一項限制。請將更新憑證儲存在長期儲存空間中,只要憑證依然有效,就能繼續使用。如果您的應用程式要求過多的更新憑證,可能會達到這些限制,在這種情況下,舊有的更新憑證將會停止運作。

    撤銷憑證

    在某些情況下,使用者可能會想撤銷先前授予應用程式的存取權。使用者可以前往 帳戶設定撤銷存取權。詳情請參閱「具有帳戶存取權的第三方網站和應用程式」一文中的「移除網站或應用程式存取權」一節。

    此外,應用程式也可以以程式輔助的方式撤銷對應用程式的存取權。如果使用者取消訂閱、移除應用程式或應用程式所需的 API 資源有大幅變動的情況,程式輔助撤銷就相當重要。換句話說,移除程序的一部分可能包含 API 要求,以確保先前授予應用程式的權限已移除。

    PHP

    如要透過程式撤銷憑證,請呼叫 revokeToken()

    $client->revokeToken();

    Python

    如要透過程式撤銷憑證,請向 https://oauth2.googleapis.com/revoke 發出要求,並將憑證做為參數,並設定 Content-Type 標頭:

    requests.post('https://oauth2.googleapis.com/revoke',
        params={'token': credentials.token},
        headers = {'content-type': 'application/x-www-form-urlencoded'})

    Ruby

    如要透過程式撤銷憑證,請向 oauth2.revoke 端點發出 HTTP 要求:

    uri = URI('https://oauth2.googleapis.com/revoke')
    response = Net::HTTP.post_form(uri, 'token' => auth_client.access_token)
    

    這個權杖可以是存取權杖或更新權杖。如果符記是存取權杖,且具有對應的重新整理權杖,則系統會一併撤銷更新權杖。

    如果撤銷作業成功,則回應狀態碼為 200。如果是錯誤條件,系統會傳回狀態碼 400 以及錯誤代碼。

    Node.js

    如要透過程式撤銷憑證,請向 /revoke 端點發出 HTTPS POST 要求:

    const https = require('https');
    
    // Build the string for the POST request
    let postData = "token=" + userCredential.access_token;
    
    // Options for POST request to Google's OAuth 2.0 server to revoke a token
    let postOptions = {
      host: 'oauth2.googleapis.com',
      port: '443',
      path: '/revoke',
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Content-Length': Buffer.byteLength(postData)
      }
    };
    
    // Set up the request
    const postReq = https.request(postOptions, function (res) {
      res.setEncoding('utf8');
      res.on('data', d => {
        console.log('Response: ' + d);
      });
    });
    
    postReq.on('error', error => {
      console.log(error)
    });
    
    // Post the request with data
    postReq.write(postData);
    postReq.end();
    

    權杖參數可以是存取權杖或更新權杖。如果符記是存取權杖,且具有對應的重新整理權杖,則系統會一併撤銷更新權杖。

    如果撤銷作業成功,則回應狀態碼為 200。如果是錯誤條件,系統會傳回狀態碼 400 以及錯誤代碼。

    HTTP/REST

    如要透過程式撤銷憑證,應用程式會向 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 以及錯誤代碼。