本文件說明網路伺服器應用程式如何使用 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:
- Open the API Library (位於 Google API Console)。
- If prompted, select a project, or create a new one.
- API Library 會列出所有可用的 API,依產品系列和熱門程度分組。如果清單中未顯示您想啟用的 API,請使用搜尋功能尋找該 API,或在其所屬的產品系列中按一下「View All」。
- 選取要啟用的 API,然後按一下「Enable」按鈕。
- If prompted, enable billing.
- If prompted, read and accept the API's Terms of Service.
建立授權憑證
任何使用 OAuth 2.0 存取 Google API 的應用程式都必須具備授權憑證,可用來向 Google 的 OAuth 2.0 伺服器識別該應用程式。下列步驟說明如何為專案建立憑證。接著,應用程式就能使用憑證存取您為該專案啟用的 API。
- Go to the Credentials page.
- 按一下 [Create credentials] (建立憑證) > [OAuth client ID] (OAuth 用戶端 ID)。
- 選取「Web application」(網頁應用程式) 應用程式類型。
- 填寫表單然後按一下「建立」。如果應用程式使用 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-auth
、google-auth-oauthlib
和google-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.6 以上版本
-
Ruby 適用的 Google 驗證程式庫:
gem install googleauth
-
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 要求。
下方清單快速總結這些步驟:
- 應用程式會識別所需的權限。
- 應用程式會將使用者以及要求的權限清單重新導向至 Google。
- 使用者可決定是否要將權限授予您的應用程式。
- 您的應用程式會顯示使用者做出的決定。
- 如果使用者授予了要求的權限,應用程式就會擷取代表使用者發出 API 要求所需的權杖。
步驟 1:設定授權參數
首先,請建立授權要求。該要求會設定用來識別應用程式的參數,並定義使用者必須授予應用程式的權限。
- 如果您使用 Google 用戶端程式庫進行 OAuth 2.0 驗證與授權,就需要建立並設定定義這些參數的物件。
- 如果直接呼叫 Google OAuth 2.0 端點,則會產生網址,並設定該網址的參數。
下列分頁會定義網路伺服器應用程式支援的授權參數。特定語言的範例還示範如何使用用戶端程式庫或授權程式庫,設定用來設定這些參數的物件。
PHP
下列程式碼片段會建立 Google\Client()
物件,用來定義授權要求中的參數。
這個物件會使用 client_secret.json 檔案中的資訊來識別應用程式。(如要進一步瞭解該檔案,請參閱「建立授權憑證」一節)。這個物件也會識別應用程式要求存取的權限範圍,以及用於處理 Google OAuth 2.0 伺服器回應的應用程式驗證端點網址。最後,程式碼會設定選用的 access_type
和 include_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" will prompt the user for consent $client->setPrompt('consent'); $client->setIncludeGrantedScopes(true); // incremental auth
這項要求會指定以下資訊:
參數 | |||||||
---|---|---|---|---|---|---|---|
client_id |
必要
應用程式的用戶端 ID。您可以在 API Console Credentials page中找到這個值。 在 PHP 中,呼叫 $client = new Google\Client(); $client->setAuthConfig('client_secret.json'); |
||||||
redirect_uri |
必要
決定 API 伺服器在使用者完成授權流程後,將使用者重新導向的位置。這個值必須與 OAuth 2.0 用戶端的其中一個已授權重新導向 URI 完全相符,而您在用戶端的 API Console
Credentials page中設定該用戶端。如果這個值與所提供 請注意, 如要在 PHP 中設定這個值,請呼叫 $client->setRedirectUri('https://oauth2.example.com/code'); |
||||||
scope |
必要
以空格分隔的範圍清單,用於識別應用程式可代表使用者存取的資源。這些值會通知 Google 向使用者顯示的同意畫面。 範圍可讓應用程式僅要求存取所需資源,同時讓使用者能控管對應用程式的存取權量。因此,要求的範圍數量與取得使用者同意聲明的可能性之間存在反向關係。 如要在 PHP 中設定這個值,請呼叫 $client->addScope(Google\Service\Drive::DRIVE_METADATA_READONLY); 建議您讓應用程式盡可能在相關情境下要求存取授權範圍。透過漸進式授權,在使用者情境中要求存取使用者資料,有助於使用者更輕鬆地瞭解應用程式需要取得存取權的原因。 |
||||||
access_type |
建議
指出使用者未在瀏覽器中出現時,應用程式能否重新整理存取權杖。有效參數值為 如果應用程式需要在使用者未在瀏覽器中顯示時更新存取權杖,請將值設為 如要在 PHP 中設定這個值,請呼叫 $client->setAccessType('offline'); |
||||||
state |
建議
指定應用程式使用的任何字串值,在授權要求和授權伺服器回應之間維持狀態。使用者同意或拒絕應用程式的存取要求之後,伺服器會傳回您在 這項參數可用於多種用途,例如將使用者導向應用程式中的正確資源、傳送 Nonce,以及減少跨網站要求偽造情形。由於 如要在 PHP 中設定這個值,請呼叫 $client->setState($sample_passthrough_value); |
||||||
include_granted_scopes |
選用 可讓應用程式使用漸進式授權功能,在情境中要求存取其他範圍。如果將這個參數的值設為 如要在 PHP 中設定這個值,請呼叫 $client->setIncludeGrantedScopes(true); |
||||||
enable_granular_consent |
選用 預設為 |
||||||
login_hint |
選用 如果應用程式知道要驗證哪位使用者,就能使用這個參數向 Google 驗證伺服器提供提示。伺服器會使用提示來簡化登入流程,方法是在登入表單中預先填入電子郵件欄位,或是選取適當的多登入工作階段。 將參數值設為電子郵件地址或 如要在 PHP 中設定這個值,請呼叫 $client->setLoginHint('None'); |
||||||
prompt |
選用 顯示使用者的提示清單 (以空格分隔且區分大小寫)。如未指定這個參數,系統只會在專案第一次要求存取權時提示使用者。詳情請參閱「 提示重新取得同意聲明」一文。 如要在 PHP 中設定這個值,請呼叫 $client->setPrompt('consent'); 可能的值為:
|
Python
下列程式碼片段使用 google-auth-oauthlib.flow
模組建構授權要求。
程式碼會建構 Flow
物件,該物件會使用您在建立授權憑證後下載的 client_secret.json 檔案中的資訊。這個物件也會識別應用程式要求存取的權限範圍,以及用於處理 Google OAuth 2.0 伺服器回應的應用程式驗證端點網址。最後,程式碼會設定選用的 access_type
和 include_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 中,呼叫 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 伺服器在使用者完成授權流程後,將使用者重新導向的位置。這個值必須與 OAuth 2.0 用戶端的其中一個已授權重新導向 URI 完全相符,而您在用戶端的 API Console
Credentials page中設定該用戶端。如果這個值與所提供 請注意, 如要在 Python 中設定這個值,請設定 flow.redirect_uri = 'https://oauth2.example.com/code' |
||||||
scope |
必要
範圍清單,用於識別應用程式可代表使用者存取的資源。這些值會通知 Google 向使用者顯示的同意畫面。 範圍可讓應用程式僅要求存取所需資源,同時讓使用者能控管對應用程式的存取權量。因此,要求的範圍數量與取得使用者同意聲明的可能性之間存在反向關係。 在 Python 中,使用與設定 flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( 'client_secret.json', scopes=['https://www.googleapis.com/auth/drive.metadata.readonly']) 建議您讓應用程式盡可能在相關情境下要求存取授權範圍。透過漸進式授權,在使用者情境中要求存取使用者資料,有助於使用者更輕鬆地瞭解應用程式需要取得存取權的原因。 |
||||||
access_type |
建議
指出使用者未在瀏覽器中出現時,應用程式能否重新整理存取權杖。有效參數值為 如果應用程式需要在使用者未在瀏覽器中顯示時更新存取權杖,請將值設為 在 Python 中呼叫 authorization_url, state = flow.authorization_url( access_type='offline', include_granted_scopes='true') |
||||||
state |
建議
指定應用程式使用的任何字串值,在授權要求和授權伺服器回應之間維持狀態。使用者同意或拒絕應用程式的存取要求之後,伺服器會傳回您在 這項參數可用於多種用途,例如將使用者導向應用程式中的正確資源、傳送 Nonce,以及減少跨網站要求偽造情形。由於 在 Python 中呼叫 authorization_url, state = flow.authorization_url( access_type='offline', state=sample_passthrough_value, include_granted_scopes='true') |
||||||
include_granted_scopes |
選用 可讓應用程式使用漸進式授權功能,在情境中要求存取其他範圍。如果將這個參數的值設為 在 Python 中呼叫 authorization_url, state = flow.authorization_url( access_type='offline', include_granted_scopes='true') |
||||||
enable_granular_consent |
選用 預設為 |
||||||
login_hint |
選用 如果應用程式知道要驗證哪位使用者,就能使用這個參數向 Google 驗證伺服器提供提示。伺服器會使用提示來簡化登入流程,方法是在登入表單中預先填入電子郵件欄位,或是選取適當的多登入工作階段。 將參數值設為電子郵件地址或 在 Python 中呼叫 authorization_url, state = flow.authorization_url( access_type='offline', login_hint='None', include_granted_scopes='true') |
||||||
prompt |
選用 顯示使用者的提示清單 (以空格分隔且區分大小寫)。如未指定這個參數,系統只會在專案第一次要求存取權時提示使用者。詳情請參閱「 提示重新取得同意聲明」一文。 在 Python 中呼叫 authorization_url, state = flow.authorization_url( access_type='offline', prompt='consent', include_granted_scopes='true') 可能的值為:
|
Ruby
使用您建立的 client_secrets.json 檔案來設定應用程式中的用戶端物件。設定用戶端物件時,您必須指定應用程式需要存取的範圍,以及應用程式的驗證端點網址,以便處理 OAuth 2.0 伺服器的回應。
舉例來說,以下程式碼會要求取得使用者 Google 雲端硬碟的唯讀存取權:
require 'google/apis/drive_v3' require "googleauth" require 'googleauth/stores/redis_token_store' client_id = Google::Auth::ClientId.from_file('/path/to/client_secret.json') scope = 'https://www.googleapis.com/auth/drive.metadata.readonly' token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, '/oauth2callback')Your application uses the client object to perform OAuth 2.0 operations, such as generating authorization request URLs and applying access tokens to HTTP requests.
Node.js
The code snippet below creates a google.auth.OAuth2
object, which defines the
parameters in the authorization request.
That object uses information from your client_secret.json file to identify your application. To ask for permissions from a user to retrieve an access token, you redirect them to a consent page. To create a consent page URL:
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 伺服器在使用者完成授權流程後,將使用者重新導向的位置。這個值必須與 OAuth 2.0 用戶端的其中一個已授權重新導向 URI 完全相符,而您在用戶端的 API Console
Credentials page中設定該用戶端。如果這個值與所提供 請注意, |
||||||
response_type |
必要
判斷 Google OAuth 2.0 端點是否傳回授權碼。 將網路伺服器應用程式的參數值設為 |
||||||
scope |
必要
以空格分隔的範圍清單,用於識別應用程式可代表使用者存取的資源。這些值會通知 Google 向使用者顯示的同意畫面。 範圍可讓應用程式僅要求存取所需資源,同時讓使用者能控管對應用程式的存取權量。因此,要求的範圍數量與取得使用者同意聲明的可能性之間存在反向關係。 建議您讓應用程式盡可能在相關情境下要求存取授權範圍。透過漸進式授權,在使用者情境中要求存取使用者資料,有助於使用者更輕鬆地瞭解應用程式需要取得存取權的原因。 |
||||||
access_type |
建議
指出使用者未在瀏覽器中出現時,應用程式能否重新整理存取權杖。有效參數值為 如果應用程式需要在使用者未在瀏覽器中顯示時更新存取權杖,請將值設為 |
||||||
state |
建議
指定應用程式使用的任何字串值,在授權要求和授權伺服器回應之間維持狀態。使用者同意或拒絕應用程式的存取要求之後,伺服器會傳回您在 這項參數可用於多種用途,例如將使用者導向應用程式中的正確資源、傳送 Nonce,以及減少跨網站要求偽造情形。由於 |
||||||
include_granted_scopes |
選用 可讓應用程式使用漸進式授權功能,在情境中要求存取其他範圍。如果將這個參數的值設為 |
||||||
enable_granular_consent |
選用 預設為 |
||||||
login_hint |
選用 如果應用程式知道要驗證哪位使用者,就能使用這個參數向 Google 驗證伺服器提供提示。伺服器會使用提示來簡化登入流程,方法是在登入表單中預先填入電子郵件欄位,或是選取適當的多登入工作階段。 將參數值設為電子郵件地址或 |
||||||
prompt |
選用 顯示使用者的提示清單 (以空格分隔且區分大小寫)。如未指定這個參數,系統只會在專案第一次要求存取權時提示使用者。詳情請參閱「 提示重新取得同意聲明」一文。 可能的值為:
|
步驟 2:重新導向至 Google 的 OAuth 2.0 伺服器
將使用者重新導向至 Google 的 OAuth 2.0 伺服器,啟動驗證和授權程序。一般而言,當應用程式首次需要存取使用者資料時,就會發生這種情況。以漸進式授權來說,當應用程式必須先存取尚未擁有存取權限的額外資源時,也適用這個步驟。
PHP
- 產生網址,以便向 Google 的 OAuth 2.0 伺服器要求存取權:
$auth_url = $client->createAuthUrl();
- 將使用者重新導向至
$auth_url
:header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
Python
以下範例說明如何使用 Flask 網頁應用程式架構,將使用者重新導向至授權網址:
return flask.redirect(authorization_url)
Ruby
- 產生網址,以便向 Google 的 OAuth 2.0 伺服器要求存取權:
auth_uri = authorizer.get_authorization_url(login_hint: user_id, request: request)
- 將使用者重新導向至
auth_uri
。
Node.js
-
使用步驟 1
generateAuthUrl
方法產生的網址authorizationUrl
,向 Google 的 OAuth 2.0 伺服器要求存取權。 -
將使用者重新導向至
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 資料」。
disallowed_useragent
授權端點會顯示在 Google OAuth 2.0 政策不允許的嵌入使用者代理程式中。
Android
Android 開發人員可能會在 android.webkit.WebView
中開啟授權要求時,看到這則錯誤訊息。
開發人員應改用 Android 程式庫,例如 Android 版 Google 登入或 OpenID Foundation 的 Android AppAuth。
如果 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) 流程。如要更新整合作業,請參閱遷移指南。
invalid_request
您提交的要求有錯誤。可能的原因如下:
- 要求格式不正確
- 要求中缺少必要參數
- 要求使用了 Google 不支援的授權方法。使用建議的整合方法驗證 OAuth 整合作業
步驟 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
在回呼頁面上,使用 googleauth
程式庫驗證授權伺服器回應。請使用 authorizer.handle_auth_callback_deferred
方法儲存授權碼,並重新導向回原先要求授權的網址。這會暫時將結果貼到使用者的工作階段,藉此延後交換程式碼。
target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) redirect target_url
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 |
在指定的 client_id 的 API Console
Credentials page 中,專案的其中一個重新導向 URI。 |
以下程式碼片段為要求範例:
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 |
可以用來取得新的存取權杖的憑證。重新整理權杖的效力會持續至使用者撤銷存取權為止。同樣地,如果您在向 Google 授權伺服器發出的初始要求中將 access_type 參數設為 offline ,這個欄位才會出現這個欄位。 |
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:
- 如果需要將存取權杖套用至新的
Google\Client
物件 (例如,如果您將存取權杖儲存在使用者工作階段),請使用setAccessToken
方法:$client->setAccessToken($access_token);
- 為要呼叫的 API 建構服務物件。您可以將授權的
Google\Client
物件提供給您要呼叫的 API 的建構函式,藉此建構服務物件。例如,如要呼叫 Drive API:$drive = new Google\Service\Drive($client);
- 使用服務物件提供的介面向 API 服務傳送要求。舉例來說,如要列出已驗證使用者的 Google 雲端硬碟中的檔案:
$files = $drive->files->listFiles(array())->getItems();
Python
取得存取權杖後,應用程式就能使用該權杖來代表指定的使用者帳戶或服務帳戶授權 API 要求。使用使用者專屬的授權憑證為要呼叫的 API 建構服務物件,然後使用該物件提出已獲授權的 API 要求。
- 為要呼叫的 API 建構服務物件。您可以呼叫
googleapiclient.discovery
程式庫的build
方法,並提供 API 的名稱和版本以及使用者憑證,藉此建構服務物件:例如呼叫 Drive API 第 3 版:from googleapiclient.discovery import build drive = build('drive', 'v2', credentials=credentials)
- 使用服務物件提供的介面向 API 服務傳送要求。舉例來說,如要列出已驗證使用者的 Google 雲端硬碟中的檔案:
files = drive.files().list().execute()
Ruby
取得存取權杖後,應用程式就能使用該憑證,代表指定的使用者帳戶或服務帳戶提出 API 要求。使用使用者專屬的授權憑證為要呼叫的 API 建構服務物件,然後使用該物件提出已獲授權的 API 要求。
- 為要呼叫的 API 建構服務物件。
舉例來說,如要呼叫 Drive API 第 3 版:
drive = Google::Apis::DriveV3::DriveService.new
- 設定服務的憑證:
drive.authorization = credentials
- 使用服務物件提供的介面向 API 服務發出要求。舉例來說,如要列出已驗證使用者的 Google 雲端硬碟中的檔案:
files = drive.list_files
或者,您也可以將 options
參數提供給方法,依個別方法提供授權:
files = drive.list_files(options: { authorization: credentials })
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。方法是加入 access_token
查詢參數或 Authorization
HTTP 標頭 Bearer
值,在向 API 發出的要求中加入存取權杖。請盡可能使用 HTTP 標頭,因為查詢字串通常會顯示在伺服器記錄中。在多數情況下,您可以使用用戶端程式庫設定對 Google API 的呼叫 (例如呼叫 Drive Files 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
如要執行這個範例,請按照下列指示操作:
- 在 API Console中,將本機電腦的網址加入重新導向網址的清單。例如,加入
http://localhost:8080
。 - 建立新目錄,然後變更為該目錄。例如:
mkdir ~/php-oauth2-example cd ~/php-oauth2-example
- 使用 Composer 安裝 PHP 適用的 Google API 用戶端程式庫:
composer require google/apiclient:^2.10
- 使用下列內容建立
index.php
和oauth2callback.php
檔案。 - 使用設定為提供 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 流程。如果您前往該網址,就會看到 4 個連結:
- 測試 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_v3' require 'sinatra' require 'googleauth' require 'googleauth/stores/redis_token_store' configure do enable :sessions set :client_id, Google::Auth::ClientId.from_file('/path/to/client_secret.json') set :scope, Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) set :authorizer, Google::Auth::WebUserAuthorizer.new(settings.client_id, settings.scope, settings.token_store, '/oauth2callback') end get '/' do user_id = settings.client_id.id credentials = settings.authorizer.get_credentials(user_id, request) if credentials.nil? redirect settings.authorizer.get_authorization_url(login_hint: user_id, request: request) end drive = Google::Apis::DriveV3::DriveService.new files = drive.list_files(options: { authorization: credentials }) "<pre>#{JSON.pretty_generate(files.to_h)}</pre>" end get '/oauth2callback' do target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) redirect target_url end
Node.js
如要執行這個範例,請按照下列指示操作:
-
在 API Console中,將本機電腦的網址加入重新導向網址清單中,例如加入
http://localhost
。 - 請確認已安裝 LTS、使用中的 LTS,或是已安裝的 Node.js 目前版本。
-
建立新目錄,然後變更為該目錄。例如:
mkdir ~/nodejs-oauth2-example cd ~/nodejs-oauth2-example
-
Install the
Google API Client
Library
for Node.js using npm:
npm install googleapis
-
使用以下內容建立
main.js
檔案。 -
執行範例:
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 位址不受這項規則的限制。 |
網域 |
“googleusercontent.com” 。goo.gl ),除非應用程式擁有網域。此外,如果擁有縮短網域的應用程式選擇重新導向至該網域,該重新導向 URI 的路徑必須包含 “/google-callback/” ,或是以 “/google-callback” 結尾。 |
使用者資訊 |
重新導向 URI 不得包含使用者資訊子元件。 |
路徑 |
重新導向 URI 不得包含路徑週遊 (又稱為目錄反向追蹤),以 |
查詢 |
重新導向 URI 不得包含開放式重新導向。 |
Fragment |
重新導向 URI 不得包含片段元件。 |
字元 |
重新導向 URI 不得包含特定字元,包括:
|
增量授權
在 OAuth 2.0 通訊協定中,您的應用程式要求存取以範圍識別的資源。建議您在需要時要求資源授權,這是最佳的使用者體驗做法。為提供這項做法,Google 的授權伺服器支援漸進式授權。這項功能可讓您視需要要求範圍,且若使用者授予新範圍的權限,則會傳回授權碼,供權杖使用,其中包含使用者授予專案的所有範圍。
舉例來說,如果應用程式會讓使用者取樣音樂曲目及建立合輯,在使用者登入時可能只需要少量資源,甚至可能只有登入者的姓名。不過,如要儲存已完成的組合,您需要存取 Google 雲端硬碟。一般而言,只有在應用程式實際需要時,使用者才要求存取 Google 雲端硬碟,往往會顯得自然。
在這種情況下,應用程式可能會在登入時要求 openid
和 profile
範圍來執行基本登入,然後在第一次要求時要求 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
,以及錯誤代碼。