本文說明網路伺服器應用程式如何使用 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,或是在其所屬的產品系列中按一下 [查看全部]。
- 選取您要啟用的 API,然後按一下 [啟用] 按鈕。
- 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)。
- 選取 [網路應用程式] 應用程式類型。
- 填寫表單,然後按一下 [建立]。使用 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 程式碼範例,您需要:
- PHP 5.4 以上版本 (含指令列介面 (CLI) 和 JSON 擴充功能)。
- Composer 依附元件管理工具。
-
適用於 PHP 的 Google API 用戶端程式庫:
php composer.phar require google/apiclient:^2.0
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.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 要求。
以下清單會概略說明這些步驟:
- 您的應用程式會識別所需權限。
- 您的應用程式會將使用者重新導向至 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" 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 ConsoleCredentials page中找到這個值。 在 PHP 中,呼叫 $client = new Google_Client(); $client->setAuthConfig('client_secret.json'); |
||||||
redirect_uri |
必要
決定 API 伺服器在使用者完成授權流程後將使用者重新導向到哪個位置。這個值必須與您在用戶端的 API ConsoleCredentials page中設定的 OAuth 2.0 用戶端授權重新導向 URI 完全相符。如果這個值與提供的 請注意, 如要在 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); |
||||||
login_hint |
選用
如果您的應用程式知道要嘗試驗證的使用者,可使用這個參數向 Google 驗證伺服器提供提示。伺服器會使用提示在登入表單中預先填入電子郵件欄位,或選取適當的多帳戶登入工作階段,藉此簡化登入流程。 將參數值設為電子郵件地址或 如要在 PHP 中設定這個值,請呼叫 $client->setLoginHint('None'); |
||||||
prompt |
選用
以空格分隔且區分大小寫的提示清單,可向使用者顯示。如未指定這個參數,系統只會在第一次要求專案存取權時提示使用者。詳情請參閱提示重新同意一文。 如要在 PHP 中設定這個值,請呼叫 $client->setApprovalPrompt('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 ConsoleCredentials 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 伺服器在使用者完成授權流程後將使用者重新導向到哪個位置。這個值必須與您在用戶端的 API ConsoleCredentials page中設定的 OAuth 2.0 用戶端授權重新導向 URI 完全相符。如果這個值與提供的 請注意, 如要在 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') |
||||||
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_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 ConsoleCredentials page中找到這個值。 |
||||||
redirect_uri |
必要
決定 API 伺服器在使用者完成授權流程後將使用者重新導向到哪個位置。這個值必須與您在用戶端的 API ConsoleCredentials page中設定的 OAuth 2.0 用戶端授權重新導向 URI 完全相符。如果這個值與提供的 請注意, |
||||||
response_type |
必要
決定 Google OAuth 2.0 端點是否傳回授權碼。 將網路伺服器應用程式的參數值設為 |
||||||
scope |
必要
以空格分隔的範圍清單,用於識別應用程式代表使用者可以存取的資源。這些值可以讓 Google 向使用者顯示同意畫面。 範圍可讓應用程式只要求存取所需資源的存取權,同時也能控管使用者授予應用程式存取權的權限。因此,要求的範圍數量與取得使用者同意的可能性之間存在逆向關係。 建議您就應用程式情況,在可行的情況下要求存取授權範圍。透過在漸進式授權的情況下,藉由在要求中存取使用者資料,可以讓使用者更容易瞭解應用程式需要其所要求存取權的原因。 |
||||||
access_type |
建議使用 指出您的應用程式是否可以在使用者沒有瀏覽器時更新存取憑證。有效參數值為 如果應用程式在使用者尚未在瀏覽器中顯示存取憑證時,請將值設為 |
||||||
state |
建議使用 指定您的應用程式用來在授權要求與授權伺服器回應之間的狀態時,所使用的任何字串值。在使用者同意或拒絕應用程式存取要求後,伺服器會傳回您在 此參數有許多用途,例如將使用者導向應用程式中的正確資源、傳送 nonce 和緩解跨網站偽造要求。由於系統可猜測您的 |
||||||
include_granted_scopes |
選用
允許應用程式使用漸進式授權,要求對內容的其他範圍進行存取。如果您將這個參數的值設為 |
||||||
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 = auth_client.authorization_uri.to_s
- 將使用者重新導向
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 資料」一文,在管理員明確授權您的 OAuth 用戶端 ID 之前,限制管理員如何限制對所有範圍或機密和受限制範圍的存取權。
disallowed_useragent
授權端點會顯示在嵌入的使用者代理程式內,且不受 Google 的 OAuth 2.0 政策規定。
Android
Android 開發人員在 android.webkit.WebView
中開啟授權要求時,可能會看到這則錯誤訊息。開發人員應改用 Android 程式庫,例如 Android 適用的 Google 登入或 OpenID Foundation (Android 專用的 AppAuth)。
如果 Android 應用程式會在嵌入的使用者代理程式中開啟一般網頁連結,而且使用者前往您的網站的 Google 的 OAuth 2.0 授權端點,網站開發人員就可能遇到這個錯誤。開發人員應允許在一般預設連結處理常式中,透過 Android App Links 處理常式或預設的瀏覽器應用程式開啟一般連結。Android 自訂分頁程式庫也是一個支援的選項。
iOS
iOS 和 macOS 開發人員在 WKWebView
中開啟授權要求時,可能會遇到這個錯誤。開發人員應改用 iOS 程式庫,例如 Google Sign-In for iOS 或 OpenID Foundation (#39;s) AppAuth for iOS。
當 iOS 或 macOS 應用程式在嵌入式使用者代理程式中開啟一般網頁連結,而且使用者前往您的網站的 Google 的 OAuth 2.0 授權端點時,網頁開發人員可能會遇到這個錯誤。開發人員應允許在一般預設連結處理常式中開啟作業系統的連結,該連結含有通用連結處理常式或預設瀏覽器應用程式。此外,系統也支援 SFSafariViewController
程式庫。
org_internal
要求中的 OAuth 用戶端 ID 屬於一項專案,會限制對特定 Google Cloud 機構中存取 Google 帳戶的專案。如要進一步瞭解這個設定選項,請參閱「設定 OAuth 同意畫面」說明文章中的「使用者類型」一節。
redirect_uri_mismatch
授權要求中傳遞的 redirect_uri
與 OAuth 用戶端 ID 的授權重新導向 URI 不符。查看 Google API Console Credentials page中已獲授權的重新導向 URI。
步驟 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 |
針對您在 client_id 的 API ConsoleCredentials 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" }
呼叫 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 第 2 版:from googleapiclient.discovery import build drive = build('drive', 'v2', credentials=credentials)
- 使用服務物件提供的介面向 API 服務傳送要求。舉例來說,如要在已驗證使用者的 Google 雲端硬碟中列出檔案,請按照下列步驟操作:
files = drive.files().list().execute()
Ruby
按照下列步驟使用 auth_client
物件呼叫 Google API:
- 為您要呼叫的 API 建構服務物件。
舉例來說,如要呼叫 Drive API 第 2 版:
drive = Google::Apis::DriveV2::DriveService.new
- 在服務中設定憑證:
drive.authorization = auth_client
- 使用服務物件提供的介面向 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 Files API 時)。
您可以試用所有 Google API,並在 OAuth 2.0 Playground 中查看其範圍。
HTTP GET 範例
使用 Authorization: Bearer
HTTP 標頭呼叫 drive.files
端點 (Drive API 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
完整範例
以下範例會在使用者驗證後,將 JSON 格式的檔案列印在使用者的 Google 雲端硬碟中,並授權應用程式存取使用者的雲端硬碟中繼資料。
PHP
如何執行此範例:
- 在 API Console中,將本機電腦的網址加入重新導向網址清單,例如
http://localhost:8080
。 - 建立新目錄並變更為該目錄。例如:
mkdir ~/php-oauth2-example cd ~/php-oauth2-example
- 使用 Composer 安裝 PHP 適用的 Google API 用戶端程式庫:
composer require google/apiclient:^2.0
- 使用下列內容建立
index.php
和oauth2callback.php
檔案。 - 使用設定提供 PHP 的網路伺服器執行範例。如果您使用的是 PHP 5.4 以上版本,可以使用 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
如何執行此範例:
-
在 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 保障應用程式的安全,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 不得包含 userinfo 子元件。 |
路徑 |
重新導向 URI 不得包含路徑遍歷 (又稱為目錄反向追蹤),由 |
查詢 |
重新導向 URI 不得包含開放式重新導向。 |
片段 |
重新導向 URI 不得包含片段元件。 |
字元 |
重新導向 URI 不得包含特定字元,包括:
|
遞增授權
在 OAuth 2.0 通訊協定中,您的應用程式會要求存取資源 (可依範圍識別)。因此,在需要資源授權時,建議您採用最佳使用者體驗。為此,Google 的授權伺服器支援漸進式授權機制。這項功能可讓您按需求要求範圍,如果使用者授予新範圍的權限,則傳回授權碼,可能會交換以作為使用者已授予專案所有範圍的符記。
舉例來說,允許使用者取得音樂曲目及建立合輯的應用程式,在登入時可能不需要極少資源,因此應該只有登入者的名稱才是資源。然而, 儲存完成的組合需要存取 Google 雲端硬碟。如果大多數人都要求在應用程式實際需要存取 Google 雲端硬碟時要求存取 Google 雲端硬碟,就會覺得資料很自然。
在這個情況下,應用程式可能會在要求時要求 openid
和 profile
範圍,以執行基本登入作業,然後再要求在首次要求時產生 https://www.googleapis.com/auth/drive.file
範圍以儲存組合。
如要導入漸進式授權機制,您必須完成一般的存取要求要求流程,並確保授權要求已包含先前授予的範圍。如此一來,您的應用程式就不必管理多個存取憑證。
下列規則適用於透過漸進式授權取得的存取憑證:
- 該憑證可用於存取與新合併授權中涵蓋的任何範圍對應的資源。
- 當您使用重新整理的權杖取得合併授權以取得存取憑證時,存取憑證代表合併的授權,可用於回應中包含的任何
scope
值。 - 合併授權包括使用者授予 API 專案的所有範圍,即使這些要求是從不同用戶端提出亦然。舉例來說,如果使用者透過電腦版用戶端授予某個範圍的存取權,然後透過行動用戶端授予另一個範圍的存取權,則合併授權就會同時納入這兩個範圍。
- 如果您撤銷代表合併授權的權杖,代表關聯使用者的所有授權範圍都會同時遭到撤銷。
步驟 2:設定授權參數以及步驟 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 時,凡是需要存取 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
以及錯誤代碼。