本文件說明如何使用 Google API 用戶端程式庫或 Google OAuth 2.0 端點,實作 OAuth 2.0 授權,以存取 YouTube Data API。
OAuth 2.0 可讓使用者與應用程式共用特定資料,同時保有使用者名稱、密碼和其他資訊的隱私。舉例來說,應用程式可以使用 OAuth 2.0 取得上傳影片至使用者 YouTube 頻道的權限。
這個 OAuth 2.0 流程專門用於使用者授權。這項功能專為可儲存機密資訊和維護狀態的應用程式而設計。經過適當授權的網頁伺服器應用程式可以在使用者與應用程式互動時,或在使用者離開應用程式後存取 API。
網頁伺服器應用程式也經常使用
服務帳戶來授權 API 要求,尤其是在呼叫 Cloud API 以存取專案資料而非使用者專屬資料時。網頁伺服器應用程式可搭配使用服務帳戶和使用者授權。請注意,YouTube Data API 僅支援服務帳戶流程,且僅適用於擁有及管理多個 YouTube 頻道的 YouTube 內容擁有者。具體來說,內容擁有者可以使用服務帳戶呼叫支援 onBehalfOfContentOwner
要求參數的 API 方法。
用戶端程式庫
本頁面的語言專屬範例會使用 Google API 用戶端程式庫來實作 OAuth 2.0 授權。如要執行程式碼範例,您必須先安裝所用語言的用戶端程式庫。
當您使用 Google API 用戶端程式庫來處理應用程式的 OAuth 2.0 流程時,用戶端程式庫會執行許多應用程式原本需要自行處理的動作。舉例來說,它會決定應用程式何時可以使用或重新整理已儲存的存取權權杖,以及應用程式何時必須重新取得同意聲明。用戶端程式庫也會產生正確的重新導向網址,並協助實作可將授權碼換成存取權杖的重新導向處理常式。
伺服器端應用程式的 Google API 用戶端程式庫支援以下語言:
必要條件
為專案啟用 API
任何會呼叫 Google API 的應用程式,都必須在 中啟用這些 API。
如要為專案啟用 API,請按照下列步驟操作:
- 在 中。
- 使用「Library」頁面找出並啟用 YouTube Data API。找出應用程式將使用的任何其他 API,並啟用這些 API。
建立授權憑證
任何使用 OAuth 2.0 存取 Google API 的應用程式,都必須具備授權憑證,才能向 Google 的 OAuth 2.0 伺服器識別應用程式。下列步驟說明如何為專案建立憑證。應用程式就能使用憑證存取您為該專案啟用的 API。
- 按一下 [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。建議您設計應用程式的驗證端點,以免應用程式將授權碼公開給網頁上的其他資源。
建立憑證後,請從 下載 client_secret.json 檔案。請將該檔案安全地儲存在只有您的應用程式可存取的位置。
找出存取權範圍
範圍可讓應用程式僅要求存取其需要的資源,也能讓使用者控制對應用程式授予的存取量。因此,要求的範圍數量與取得使用者同意的可能性之間可能呈現反比關係。
開始實作 OAuth 2.0 授權之前,建議您找出應用程式需要權限存取的範圍。
此外,我們也建議您透過漸進式授權程序,要求應用程式存取授權範圍,讓應用程式在相關使用情境中要求存取使用者資料。這個最佳做法可協助使用者更容易瞭解應用程式為何需要要求的存取權。
YouTube Data API v3 會使用下列範圍:
狙擊鏡 | |
---|---|
https://www.googleapis.com/auth/youtube | 管理您的 YouTube 帳戶 |
https://www.googleapis.com/auth/youtube.channel-memberships.creator | 查看您現有的有效頻道會員清單,以及這些會員目前的級別和成為會員的時間點 |
https://www.googleapis.com/auth/youtube.force-ssl | 查看、編輯並永久刪除您的 YouTube 影片、評分、留言和字幕 |
https://www.googleapis.com/auth/youtube.readonly | 查看您的 YouTube 帳戶 |
https://www.googleapis.com/auth/youtube.upload | 管理您的 YouTube 影片 |
https://www.googleapis.com/auth/youtubepartner | 查看及管理您在 YouTube 上的元素和相關內容 |
https://www.googleapis.com/auth/youtubepartner-channel-audit | 查看您的 YouTube 頻道中與 YouTube 合作夥伴稽核程序相關的私人資訊 |
「OAuth 2.0 API 範圍」文件包含您可能用於存取 Google API 的範圍完整清單。
語言相關規定
如要執行本文件中的任何程式碼範例,您需要具備 Google 帳戶、網際網路存取權和網路瀏覽器。如果您使用的是其中一個 API 用戶端程式庫,請參閱下方的語言專屬要求。
PHP
如要執行本文件中的 PHP 程式碼範例,您需要:
- 已安裝指令列介面 (CLI) 和 JSON 擴充功能的 PHP 8.0 以上版本。
- Composer 依附元件管理工具。
-
PHP 適用的 Google API 用戶端程式庫:
composer require google/apiclient:^2.15.0
詳情請參閱 適用於 PHP 的 Google API 用戶端程式庫。
Python
如要執行本文中的 Python 程式碼範例,您需要:
- Python 3.7 以上版本
- pip 套件管理工具。
- Python 適用的 Google API 用戶端程式庫 2.0 版:
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
如果無法升級 Python 和相關遷移指南,請查看 Google API Python 用戶端程式庫的版本資訊。
小茹
如要執行本文件中的 Ruby 程式碼範例,您需要:
- Ruby 2.6 以上版本
-
Ruby 適用的 Google 驗證程式庫:
gem install googleauth
-
Sinatra Ruby 網路應用程式架構。
gem install sinatra
Node.js
如要執行本文件中的 Node.js 程式碼範例,您需要:
- Node.js 的維護 LTS、現行 LTS 或目前版本。
-
Google API Node.js 用戶端:
npm install googleapis crypto express express-session
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
參數。
舉例來說,以下程式碼會要求離線存取權,以便管理使用者的 YouTube 帳戶:
use Google\Client; $client = new Client(); // Required, call the setAuthConfig function to load authorization credentials from // client_secret.json file. $client->setAuthConfig('client_secret.json'); // Required, to set the scope value, call the addScope function $client->addScope(GOOGLE_SERVICE_YOUTUBE::YOUTUBE_FORCE_SSL); // Required, call the setRedirectUri function to specify a valid redirect URI for the // provided client_id $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php'); // Recommended, 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'); // Recommended, call the setState function. Using a state value can increase your assurance that // an incoming connection is the result of an authentication request. $client->setState($sample_passthrough_value); // Optional, if your application knows which user is trying to authenticate, it can use this // parameter to provide a hint to the Google Authentication Server. $client->setLoginHint('hint@example.com'); // Optional, call the setPrompt function to set "consent" will prompt the user for consent $client->setPrompt('consent'); // Optional, call the setIncludeGrantedScopes function with true to enable incremental // authorization $client->setIncludeGrantedScopes(true);
Python
下列程式碼片段使用 google-auth-oauthlib.flow
模組建構授權要求。
程式碼會建構 Flow
物件,該物件會使用您在建立授權憑證後下載的 client_secret.json 檔案中的資訊來識別應用程式。該物件也會指出應用程式要求存取權的範圍,以及應用程式驗證端點的網址,後者會處理 Google OAuth 2.0 伺服器的回應。最後,程式碼會設定選用的 access_type
和 include_granted_scopes
參數。
舉例來說,以下程式碼會要求離線存取權,以便管理使用者的 YouTube 帳戶:
import google.oauth2.credentials import google_auth_oauthlib.flow # Required, call the from_client_secrets_file method to retrieve the client ID from a # client_secret.json file. The client ID (from that file) and access scopes are required. (You can # also use the from_client_config method, which passes the client configuration as it originally # appeared in a client secrets file but doesn't access the file itself.) flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file('client_secret.json', scopes=['https://www.googleapis.com/auth/youtube.force-ssl']) # Required, 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( # Recommended, 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', # Optional, enable incremental authorization. Recommended as a best practice. include_granted_scopes='true', # Optional, if your application knows which user is trying to authenticate, it can use this # parameter to provide a hint to the Google Authentication Server. login_hint='hint@example.com', # Optional, set prompt to 'consent' will prompt the user for consent prompt='consent')
小茹
使用您建立的 client_secrets.json 檔案,在應用程式中設定用戶端物件。設定用戶端物件時,您需要指定應用程式需要存取的權限範圍,以及應用程式驗證端點的網址,後者會處理 OAuth 2.0 伺服器的回應。
舉例來說,以下程式碼會要求離線存取權,以便管理使用者的 YouTube 帳戶:
require 'googleauth' require 'googleauth/web_user_authorizer' require 'googleauth/stores/redis_token_store' require 'google/apis/youtube_v3' # Required, call the from_file method to retrieve the client ID from a # client_secret.json file. client_id = Google::Auth::ClientId.from_file('/path/to/client_secret.json') # Required, scope value scope = 'https://www.googleapis.com/auth/youtube.force-ssl' # Required, Authorizers require a storage instance to manage long term persistence of # access and refresh tokens. token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) # Required, 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. callback_uri = '/oauth2callback' # 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. authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, callback_uri)
應用程式會使用用戶端物件執行 OAuth 2.0 作業,例如產生授權要求網址,以及將存取權憑證套用至 HTTP 要求。
Node.js
以下程式碼片段會建立 google.auth.OAuth2
物件,定義授權要求中的參數。
該物件會使用 client_secret.json 檔案中的資訊來識別您的應用程式。如要向使用者要求存取權,以便擷取存取權杖,您可以將使用者重新導向至同意頁面。如何建立同意聲明頁面網址:
const {google} = require('googleapis'); const crypto = require('crypto'); const express = require('express'); const session = require('express-session'); /** * 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 YouTube API const scopes = [ 'https://www.googleapis.com/auth/youtube.force-ssl' ]; // Generate a secure random state value. const state = crypto.randomBytes(32).toString('hex'); // Store state in the session req.session.state = state; // Generate a url that asks permissions for the Drive activity and Google Calendar 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, // Include the state parameter to reduce the risk of CSRF attacks. state: state });
重要注意事項:refresh_token
只會在首次授權時傳回。詳情請參閱
這篇文章。
HTTP/REST
Google 的 OAuth 2.0 端點位於 https://accounts.google.com/o/oauth2/v2/auth
。這個端點只能透過 HTTPS 存取。系統會拒絕簡易 HTTP 連線。
Google 授權伺服器支援下列查詢字串參數,適用於網路伺服器應用程式:
參數 | |||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
client_id |
必填
應用程式的用戶端 ID。您可以在 中找到這個值。 |
||||||||||||||||
redirect_uri |
必填:
決定 API 伺服器在使用者完成授權流程後,將使用者重新導向至哪個位置。這個值必須與 OAuth 2.0 用戶端的授權重新導向 URI 完全相符,您可以在用戶端的
中設定這項值。如果這個值與提供的 請注意, |
||||||||||||||||
response_type |
必填:
判斷 Google OAuth 2.0 端點是否會傳回授權碼。 將參數值設為網路伺服器應用程式的 |
||||||||||||||||
scope |
必填
以空格分隔的範圍清單,用於識別應用程式可代表使用者存取的資源。這些值會提供 Google 向使用者顯示的同意畫面。 範圍可讓應用程式僅要求存取其需要的資源,也能讓使用者控制對應用程式授予的存取量。因此,要求的範圍數量與取得使用者同意的可能性之間存在反比關係。 YouTube Data API v3 會使用下列範圍:
「OAuth 2.0 API 範圍」文件提供您可能用於存取 Google API 的範圍完整清單。 建議您盡可能在情境中要求應用程式存取授權範圍。透過漸進式授權,在相關情境下要求存取使用者資料,有助於使用者更容易瞭解應用程式為何需要取得所要求的存取權。 |
||||||||||||||||
access_type |
建議
指出應用程式是否可以在使用者不在瀏覽器時重新整理存取權存證。有效的參數值為 如果應用程式需要在使用者不在瀏覽器時重新整理存取權存證,請將值設為 |
||||||||||||||||
state |
建議
指定應用程式用於在授權要求和授權伺服器回應之間維持狀態的任何字串值。在使用者同意或拒絕應用程式的存取要求後,伺服器會傳回您在 您可以將這個參數用於多種用途,例如將使用者導向應用程式中的正確資源、傳送 Nonce,以及減輕跨網站請求偽造問題。由於 |
||||||||||||||||
include_granted_scopes |
選填:
讓應用程式使用漸進式授權,要求在情境中存取其他範圍的權限。如果您將這個參數的值設為 |
||||||||||||||||
enable_granular_consent |
選填:
預設為 當 Google 為應用程式啟用精細權限時,這個參數就不會再產生任何影響。 |
||||||||||||||||
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)
小茹
- 產生網址,向 Google 的 OAuth 2.0 伺服器要求存取權:
auth_uri = authorizer.get_authorization_url(request: request)
- 將使用者重新導向至
auth_uri
。
Node.js
-
使用 步驟 1
generateAuthUrl
方法產生的網址authorizationUrl
,向 Google 的 OAuth 2.0 伺服器要求存取權。 -
將使用者重新導向至
authorizationUrl
。res.redirect(authorizationUrl);
HTTP/REST
將重新導向作業重導至 Google 授權伺服器的範例
下方的範例網址會要求離線存取權 (access_type=offline
),讓範圍可存取使用者的 YouTube 帳戶。它會使用漸進式授權機制,確保新的存取權憑證涵蓋使用者先前授予應用程式存取權的所有範圍。網址也會為必要的 redirect_uri
、response_type
和 client_id
參數以及 state
參數設定值。網址包含換行符號和空格,方便閱讀。
https://accounts.google.com/o/oauth2/v2/auth?
scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.readonly&
access_type=offline&
include_granted_scopes=true&
state=state_parameter_passthrough_value&
redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback&
response_type=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 帳戶無法授權所要求的一或多個範圍。如要進一步瞭解管理員如何限制對所有範圍或機密和受限制範圍的存取權,直到明確授予 OAuth 客戶端 ID 存取權為止,請參閱 Google Workspace 管理員說明文章「 控管哪些第三方應用程式和內部應用程式可存取 Google Workspace 資料」。
disallowed_useragent
授權端點會顯示在 Google 的 OAuth 2.0 政策禁止使用的嵌入式使用者代理程式中。
Android
Android 開發人員在 android.webkit.WebView
中開啟授權要求時,可能會遇到這則錯誤訊息。開發人員應改用 Android 程式庫,例如 Google 登入 (Android 版) 或 OpenID Foundation 的 AppAuth for Android。
如果 Android 應用程式在嵌入式使用者代理程式中開啟一般網頁連結,且使用者從您的網站前往 Google 的 OAuth 2.0 授權端點,網頁開發人員可能會遇到這個錯誤。開發人員應允許一般連結在作業系統的預設連結處理常式中開啟,包括 Android 應用程式連結處理常式或預設瀏覽器應用程式。Android 自訂分頁程式庫也是支援的選項。
iOS
iOS 和 macOS 開發人員在 WKWebView
中開啟授權要求時,可能會遇到這個錯誤。開發人員應改用 iOS 程式庫,例如 Google 登入 (適用於 iOS) 或 OpenID Foundation 的 AppAuth for iOS。
如果 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 不符。在 中查看已授權的重新導向 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 雲端硬碟中檔案的中繼資料,以及查看 Google 日曆活動的只讀存取權,藉此測試這個流程:
https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.readonly& access_type=offline& include_granted_scopes=true& state=state_parameter_passthrough_value& redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback& response_type=code& client_id=client_id
完成 OAuth 2.0 流程後,系統應會將您重新導向至 http://localhost/oauth2callback
,但除非本機在該位址提供檔案,否則可能會產生 404 NOT FOUND
錯誤。在下一個步驟中,我們會進一步說明使用者重新導向至應用程式時,URI 中傳回的資訊。
步驟 5:交換授權碼,取得更新和存取權杖
網頁伺服器收到授權碼後,即可用授權碼交換存取權杖。
PHP
如要將授權碼換成存取權杖,請使用 fetchAccessTokenWithAuthCode
方法:
$access_token = $client->fetchAccessTokenWithAuthCode($_GET['code']);
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/youtube.force-ssl'], 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, 'granted_scopes': credentials.granted_scopes}
小茹
在回呼頁面上,使用 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. app.get('/oauth2callback', async (req, res) => { 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 if (q.state !== req.session.state) { //check state value console.log('State mismatch. Possible CSRF attack'); res.end('State mismatch. Possible CSRF attack'); } else { // 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 |
從 取得的用戶端 ID。 |
client_secret |
從 取得的用戶端密鑰。 |
code |
初始要求傳回的授權碼。 |
grant_type |
如 OAuth 2.0 規格所定義,這個欄位的值必須設為 authorization_code 。 |
redirect_uri |
在 中為專案列出的重新導向 URI 之一,可用於指定 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 物件來回應這項要求,其中包含短效存取權杖和重新整理權杖。請注意,只有在應用程式將 access_type
參數設為 offline
時,系統才會在向 Google 授權伺服器提出的初始要求中傳回更新權杖。
回應包含下列欄位:
欄位 | |
---|---|
access_token |
應用程式傳送的權杖,用於授權 Google API 要求。 |
expires_in |
存取權杖的剩餘生命週期 (以秒為單位)。 |
refresh_token |
您可以使用這項權杖取得新的存取權杖。重新整理權杖在使用者撤銷存取權之前有效。再次提醒,只有在您將 access_type 參數設為 Google 授權伺服器的初始要求中的 offline 時,這個欄位才會出現在回應中。 |
scope |
access_token 授予的存取範圍,以空格分隔的字串清單表示,並區分大小寫。 |
token_type |
傳回的符記類型。此時,這個欄位的值一律會設為 Bearer 。 |
以下程式碼片段為回應範例:
{ "access_token": "1/fFAGRNJru1FTz70BzhT3Zg", "expires_in": 3920, "token_type": "Bearer", "scope": "https://www.googleapis.com/auth/youtube.force-ssl", "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" }
錯誤
當您使用授權碼兌換存取權杖時,可能會遇到下列錯誤,而非預期的回應。以下列出常見的錯誤代碼和建議解決方案。
invalid_grant
您提供的授權碼無效或格式錯誤。重新啟動 OAuth 程序,要求新的驗證碼,以便再次向使用者索取同意聲明。
步驟 6:檢查使用者授予的範圍
一次要求多個範圍時,使用者可能不會授予應用程式要求的所有範圍。應用程式應一律檢查使用者授予的存取範圍,並透過停用相關功能來處理任何存取範圍拒絕情形。詳情請參閱如何處理精細權限。
PHP
如要檢查使用者已授予哪些範圍,請使用 getGrantedScope()
方法:
// Space-separated string of granted scopes if it exists, otherwise null. $granted_scopes = $client->getOAuth2Service()->getGrantedScope();
Python
傳回的 credentials
物件具有 granted_scopes
屬性,這是使用者授予應用程式的權限清單。
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, 'granted_scopes': credentials.granted_scopes}
小茹
一次要求多個範圍時,請透過 credentials
物件的 scope
屬性,檢查系統授予哪些範圍。
# User authorized the request. Now, check which scopes were granted. if credentials.scope.include?(Google::Apis::YoutubeV3::AUTH_YOUTUBE_FORCE_SSL) # User authorized permission to see, edit, and permanently delete the # YouTube videos, ratings, comments and captions. # Calling the APIs, etc else # User didn't authorize the permission. # Update UX and application accordingly end
Node.js
一次要求多個範圍時,請透過 tokens
物件的 scope
屬性,檢查系統授予哪些範圍。
// User authorized the request. Now, check which scopes were granted. if (tokens.scope.includes('https://www.googleapis.com/auth/youtube.force-ssl')) { // User authorized permission to see, edit, and permanently delete the // YouTube videos, ratings, comments and captions. // Calling the APIs, etc. } else { // User didn't authorize read-only Drive activity permission. // Update UX and application accordingly }
HTTP/REST
如要檢查使用者是否已授予應用程式存取特定範圍的權限,請查看存取權存取金鑰回應中的 scope
欄位。由 access_token 授予的存取權範圍,以空格分隔的字串清單表示,並區分大小寫。
舉例來說,下列存取權杖回應範例指出,使用者已授予您的應用程式權限,可查看、編輯及永久刪除使用者的 YouTube 影片、評分、留言和字幕:
{ "access_token": "1/fFAGRNJru1FTz70BzhT3Zg", "expires_in": 3920, "token_type": "Bearer", "scope": "https://www.googleapis.com/auth/youtube.force-ssl", "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" }
呼叫 Google API
PHP
如要使用存取權杖呼叫 Google API,請完成下列步驟:
- 如果您需要將存取權杖套用至新的
Google\Client
物件 (例如,如果您在使用者工作階段中儲存存取權杖),請使用setAccessToken
方法:$client->setAccessToken($access_token);
- 為要呼叫的 API 建構服務物件。您可以為要呼叫的 API 建構函式提供已授權的
Google\Client
物件,藉此建構服務物件。舉例來說,如要呼叫 YouTube Data API,請按照下列步驟操作:$youtube = new Google_Service_YouTube($client);
- 使用
服務物件提供的介面,向 API 服務提出要求。舉例來說,如要擷取授權使用者的 YouTube 頻道相關資料,請按照下列步驟操作:
$channel = $youtube->channels->listChannels('snippet', array('mine' => $mine));
Python
取得存取權杖後,應用程式便可使用該權杖,代表特定使用者帳戶或服務帳戶授權 API 要求。使用使用者專屬的授權憑證,為要呼叫的 API 建構服務物件,然後使用該物件提出已授權的 API 要求。
- 為要呼叫的 API 建構服務物件。您可以呼叫
googleapiclient.discovery
程式庫的build
方法,並傳入 API 的名稱和版本,以及使用者憑證,藉此建構服務物件: 例如,呼叫 YouTube Data API 3 版:from googleapiclient.discovery import build youtube = build('youtube', 'v3', credentials=credentials)
- 使用服務物件提供的介面,向 API 服務提出要求。舉例來說,如要擷取授權使用者的 YouTube 頻道相關資料,請按照下列步驟操作:
channel = youtube.channels().list(mine=True, part='snippet').execute()
小茹
取得存取權杖後,應用程式就能使用該權杖,代表特定使用者帳戶或服務帳戶提出 API 要求。使用使用者專屬的授權憑證,為要呼叫的 API 建構服務物件,然後使用該物件提出已授權的 API 要求。
- 為要呼叫的 API 建構服務物件。舉例來說,如要呼叫 YouTube Data API 第 3 版:
youtube = Google::Apis::YoutubeV3::YouTubeService.new
- 設定服務的憑證:
youtube.authorization = credentials
- 使用服務物件提供的介面,向 API 服務提出要求。舉例來說,如要擷取授權使用者的 YouTube 頻道相關資料,請按照下列步驟操作:
channel = youtube.list_channels(part, :mine => mine)
或者,您也可以為每個方法提供授權,方法是將 options
參數提供給方法:
channel = youtube.list_channels(part, :mine => mine, options: { authorization: auth_client })
Node.js
取得存取權杖並將其設為 OAuth2
物件後,請使用該物件呼叫 Google API。應用程式可以使用該權杖,代表特定使用者帳戶或服務帳戶授權 API 要求。為要呼叫的 API 建構服務物件。舉例來說,以下程式碼會使用 Google Drive API 列出使用者雲端硬碟中的檔案名稱。
const { google } = require('googleapis'); // Example of using YouTube API to list channels. var service = google.youtube('v3'); service.channels.list({ auth: oauth2Client, part: 'snippet,contentDetails,statistics', forUsername: 'GoogleDevelopers' }, function (err, response) { if (err) { console.log('The API returned an error: ' + err); return; } var channels = response.data.items; if (channels.length == 0) { console.log('No channel found.'); } else { console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + 'it has %s views.', channels[0].id, channels[0].snippet.title, channels[0].statistics.viewCount); } });
HTTP/REST
應用程式取得存取權權杖後,如果已授予 API 所需的存取範圍,您就可以使用權杖代表特定使用者帳戶呼叫 Google API。如要這樣做,請在 API 要求中加入存取權杖,方法是加入 access_token
查詢參數或 Authorization
HTTP 標頭 Bearer
值。盡可能使用 HTTP 標頭,因為查詢字串通常會顯示在伺服器記錄中。在大多數情況下,您可以使用用戶端程式庫設定 Google API 呼叫 (例如呼叫 YouTube Data API)。
請注意,YouTube Data API 僅支援擁有及管理多個 YouTube 頻道的 YouTube 內容擁有者 (例如唱片公司和電影製片公司) 的服務帳戶。
您可以在 OAuth 2.0 Playground 中試用所有 Google API,並查看其範圍。
HTTP GET 範例
使用 Authorization: Bearer
HTTP 標頭呼叫
youtube.channels
端點 (YouTube Data API) 的呼叫可能如下所示。請注意,您需要指定自己的存取權杖:
GET /youtube/v3/channels?part=snippet&mine=true HTTP/1.1 Host: www.googleapis.com Authorization: Bearer access_token
以下是針對已驗證使用者,使用 access_token
查詢字串參數呼叫相同 API 的範例:
GET https://www.googleapis.com/youtube/v3/channels?access_token=access_token&part=snippet&mine=true
curl
範例
您可以使用 curl
指令列應用程式測試這些指令。以下是使用 HTTP 標頭選項的範例 (建議做法):
curl -H "Authorization: Bearer access_token" https://www.googleapis.com/youtube/v3/channels?part=snippet&mine=true
或者,您也可以使用查詢字串參數選項:
curl https://www.googleapis.com/youtube/v3/channels?access_token=access_token&part=snippet&mine=true
完整範例
以下範例會在使用者驗證並授權應用程式管理使用者的 YouTube 帳戶後,列印 JSON 格式的物件,顯示使用者的 YouTube 頻道相關資訊。
PHP
如何執行這個範例:
- 在 中,將本機的網址新增至重新導向網址清單。例如,新增
http://localhost:8080
。 - 建立新目錄並切換至該目錄。例如:
mkdir ~/php-oauth2-example cd ~/php-oauth2-example
- 使用 Composer 安裝 PHP 專用 Google API 用戶端程式庫:
composer require google/apiclient:^2.15.0
- 建立含有以下內容的
index.php
和oauth2callback.php
檔案。 - 使用 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_secret.json'); // User granted permission as an access token is in the session. if (isset($_SESSION['access_token']) && $_SESSION['access_token']) { $client->setAccessToken($_SESSION['access_token']); $youtube = new Google_Service_YouTube($client); $channel = $youtube->channels->listChannels('snippet', array('mine' => $mine)); echo json_encode($channel); } else { // Redirect users to outh2call.php which redirects users to Google OAuth 2.0 $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(); // Required, call the setAuthConfig function to load authorization credentials from // client_secret.json file. $client->setAuthConfigFile('client_secret.json'); $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST']. $_SERVER['PHP_SELF']); // Required, to set the scope value, call the addScope function. $client->addScope(GOOGLE_SERVICE_YOUTUBE::YOUTUBE_FORCE_SSL); // Enable incremental authorization. Recommended as a best practice. $client->setIncludeGrantedScopes(true); // Recommended, 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"); // Generate a URL for authorization as it doesn't contain code and error if (!isset($_GET['code']) && !isset($_GET['error'])) { // Generate and set state value $state = bin2hex(random_bytes(16)); $client->setState($state); $_SESSION['state'] = $state; // Generate a url that asks permissions. $auth_url = $client->createAuthUrl(); header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL)); } // User authorized the request and authorization code is returned to exchange access and // refresh tokens. if (isset($_GET['code'])) { // Check the state value if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['state']) { die('State mismatch. Possible CSRF attack.'); } // Get access and refresh tokens (if access_type is offline) $token = $client->fetchAccessTokenWithAuthCode($_GET['code']); /** Save access and refresh token to the session variables. * ACTION ITEM: In a production app, you likely want to save the * refresh token in a secure persistent storage instead. */ $_SESSION['access_token'] = $token; $_SESSION['refresh_token'] = $client->getRefreshToken(); $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/'; header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL)); } // An error response e.g. error=access_denied if (isset($_GET['error'])) { echo "Error: ". $_GET['error']; } ?>
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" # The OAuth 2.0 access scope allows for access to the # authenticated user's account and requires requests to use an SSL connection. SCOPES = ['https://www.googleapis.com/auth/youtube.force-ssl'] API_SERVICE_NAME = 'youtube' API_VERSION = 'v3' 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']) youtube = googleapiclient.discovery.build( API_SERVICE_NAME, API_VERSION, credentials=credentials) channel = youtube.channels().list(mine=True, part='snippet').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(**channel) @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, 'granted_scopes': credentials.granted_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' # This disables the requested scopes and granted scopes check. # If users only grant partial request, the warning would not be thrown. os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1' # Specify a hostname and port that are set as a valid redirect URI # for your API project in the . app.run('localhost', 8080, debug=True)
小茹
本範例使用 Sinatra 架構。
require 'googleauth' require 'googleauth/web_user_authorizer' require 'googleauth/stores/redis_token_store' require 'google/apis/youtube_v3' require 'sinatra' configure do enable :sessions # Required, call the from_file method to retrieve the client ID from a # client_secret.json file. set :client_id, Google::Auth::ClientId.from_file('/path/to/client_secret.json') # Required, scope value # Access scopes for retrieving data about the user's YouTube channel. scope = 'Google::Apis::YoutubeV3::AUTH_YOUTUBE_FORCE_SSL' # Required, Authorizers require a storage instance to manage long term persistence of # access and refresh tokens. set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) # Required, 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. set :callback_uri, '/oauth2callback' # 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. set :authorizer, Google::Auth::WebUserAuthorizer.new(settings.client_id, settings.scope, settings.token_store, callback_uri: settings.callback_uri) end get '/' do # NOTE: Assumes the user is already authenticated to the app user_id = request.session['user_id'] # Fetch stored credentials for the user from the given request session. # nil if none present credentials = settings.authorizer.get_credentials(user_id, request) if credentials.nil? # Generate a url that asks the user to authorize requested scope(s). # Then, redirect user to the url. redirect settings.authorizer.get_authorization_url(request: request) end # User authorized read-only YouTube Data API permission. # Example of using YouTube Data API to list user's YouTube channel youtube = Google::Apis::YoutubeV3::YouTubeService.new channel = youtube.list_channels(part, :mine => mine, options: { authorization: auth_client }) "<pre>#{JSON.pretty_generate(channel.to_h)}</pre>" end # Receive the callback from Google's OAuth 2.0 server. get '/oauth2callback' do # Handle the result of the oauth callback. Defers the exchange of the code by # temporarily stashing the results in the user's session. target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) redirect target_url end
Node.js
如何執行這個範例:
-
在 中,將本機的網址新增至重新導向網址清單。例如,新增
http://localhost
。 - 請確認您已安裝維護 LTS、主動 LTS 或目前的 Node.js 版本。
-
建立新目錄並切換至該目錄。例如:
mkdir ~/nodejs-oauth2-example cd ~/nodejs-oauth2-example
-
使用 npm 安裝 Node.js 適用的 Google API 用戶端程式庫:
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'); const crypto = require('crypto'); const express = require('express'); const session = require('express-session'); /** * 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 YouTube API const scopes = [ 'https://www.googleapis.com/auth/youtube.force-ssl' ]; /* 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 app = express(); app.use(session({ secret: 'your_secure_secret_key', // Replace with a strong secret resave: false, saveUninitialized: false, })); // Example on redirecting user to Google's OAuth 2.0 server. app.get('/', async (req, res) => { // Generate a secure random state value. const state = crypto.randomBytes(32).toString('hex'); // Store state in the session req.session.state = state; // Generate a url that asks permissions for the Drive activity and Google Calendar 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, // Include the state parameter to reduce the risk of CSRF attacks. state: state }); res.redirect(authorizationUrl); }); // Receive the callback from Google's OAuth 2.0 server. app.get('/oauth2callback', async (req, res) => { // 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 if (q.state !== req.session.state) { //check state value console.log('State mismatch. Possible CSRF attack'); res.end('State mismatch. Possible CSRF attack'); } 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 YouTube API to list channels. var service = google.youtube('v3'); service.channels.list({ auth: oauth2Client, part: 'snippet,contentDetails,statistics', forUsername: 'GoogleDevelopers' }, function (err, response) { if (err) { console.log('The API returned an error: ' + err); return; } var channels = response.data.items; if (channels.length == 0) { console.log('No channel found.'); } else { console.log('This channel\'s ID is %s. Its title is \'%s\', and ' + 'it has %s views.', channels[0].id, channels[0].snippet.title, channels[0].statistics.viewCount); } }); } }); // Example on revoking a token app.get('/revoke', async (req, res) => { // 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(); }); const server = http.createServer(app); server.listen(8080); } 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__) # To get these credentials (CLIENT_ID CLIENT_SECRET) and for your application, visit # https://console.cloud.google.com/apis/credentials. CLIENT_ID = '123456789.apps.googleusercontent.com' CLIENT_SECRET = 'abc123' # Read from a file or environmental variable in a real app # Access scopes for YouTube API SCOPE = 'https://www.googleapis.com/auth/youtube.force-ssl' # 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. 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/youtube/v3/channels/list' r = requests.get(req_uri, headers=headers) return r.text @app.route('/oauth2callback') def oauth2callback(): if 'code' not in flask.request.args: state = str(uuid.uuid4()) flask.session['state'] = state # Generate a url that asks permissions for the Drive activity # and Google Calendar scope. Then, redirect user to the url. auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code' '&client_id={}&redirect_uri={}&scope={}&state={}').format(CLIENT_ID, REDIRECT_URI, SCOPE, state) return flask.redirect(auth_uri) else: if 'state' not in flask.request.args or flask.request.args['state'] != flask.session['state']: return 'State mismatch. Possible CSRF attack.', 400 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'} # Exchange authorization code for access and refresh tokens (if access_type is offline) 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 (包括本機主機 IP 位址 URI)。 |
主機 |
主機不得是原始 IP 位址。本規則不適用於本機 IP 位址。 |
網域 |
“googleusercontent.com” 。goo.gl )。此外,如果擁有縮短網域的應用程式選擇重新導向至該網域,則重新導向 URI 的路徑必須包含 “/google-callback/” ,或以 “/google-callback” 結尾。 |
Userinfo |
重新導向 URI 不得包含 userinfo 子元件。 |
路徑 |
重新導向 URI 不得包含路徑遍歷 (也稱為目錄回溯),這類路徑遍歷會以 |
查詢 |
重新導向 URI 不得包含開放式重新導向。 |
Fragment |
重新導向 URI 不得包含片段元件。 |
字元 |
重新導向 URI 不得包含特定字元,包括:
|
增量授權
在 OAuth 2.0 通訊協定中,應用程式會要求存取資源的授權,這些資源會透過範圍識別。在需要資源時要求授權,是提供最佳使用者體驗的最佳做法。為實現這項做法,Google 的授權伺服器支援增量授權。這項功能可讓您視需要要求範圍,如果使用者授予新範圍的權限,系統會傳回授權碼,您可以將該授權碼換成包含使用者授予專案的所有範圍的權杖。
舉例來說,假設應用程式可協助使用者找出當地有趣的活動。這個應用程式可讓使用者觀看有關活動的影片、為影片評分,以及將影片新增至播放清單。使用者也可以透過應用程式將活動新增至 Google 日曆。
在這種情況下,應用程式可能不需要或不會要求存取任何範圍。不過,如果使用者嘗試為影片評分、將影片新增至播放清單,或執行其他 YouTube 操作,應用程式就會要求存取 https://www.googleapis.com/auth/youtube.force-ssl
範圍。同樣地,如果使用者嘗試新增日曆活動,應用程式也可以要求存取 https://www.googleapis.com/auth/calendar
範圍。
如要實作逐步授權,您必須完成要求存取權杖的一般流程,但請確保授權要求包含先前已授予的範圍。這種做法可讓應用程式避免管理多個存取權權杖。
從增量授權取得的存取權杖適用下列規則:
- 權杖可用於存取與新綜合授權中納入的任何範圍相對應的資源。
- 當您使用組合授權的重新整理權杖來取得存取權杖時,存取權杖會代表組合授權,並可用於回應中包含的任何
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')
小茹
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
在這個範例中,除了使用者已授予應用程式的其他存取權之外,呼叫應用程式還要求存取權,以便擷取使用者的 YouTube Analytics 資料。
GET https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyt-analytics.readonly& access_type=offline& state=security_token%3D138rk%3Btarget_url%3Dhttp...index& redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback& response_type=code& client_id=client_id& include_granted_scopes=true
Refreshing an access token (offline access)
Access tokens periodically expire and become invalid credentials for a related API request. You can refresh an access token without prompting the user for permission (including when the user is not present) if you requested offline access to the scopes associated with the token.
- If you use a Google API Client Library, the client object refreshes the access token as needed as long as you configure that object for offline access.
- If you are not using a client library, you need to set the
access_type
HTTP query parameter tooffline
when redirecting the user to Google's OAuth 2.0 server. In that case, Google's authorization server returns a refresh token when you exchange an authorization code for an access token. Then, if the access token expires (or at any other time), you can use a refresh token to obtain a new access token.
Requesting offline access is a requirement for any application that needs to access a Google
API when the user is not present. For example, an app that performs backup services or
executes actions at predetermined times needs to be able to refresh its access token when the
user is not present. The default style of access is called online
.
Server-side web applications, installed applications, and devices all obtain refresh tokens during the authorization process. Refresh tokens are not typically used in client-side (JavaScript) web applications.
PHP
If your application needs offline access to a Google API, set the API client's access type to
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。用戶端物件會視需要重新整理存取權權杖。
小茹
如果應用程式需要離線存取 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
如要重新整理存取權杖,應用程式會向 Google 的授權伺服器 (https://oauth2.googleapis.com/token
) 傳送 HTTPS POST
要求,其中包含下列參數:
欄位 | |
---|---|
client_id |
從 取得的用戶端 ID。 |
client_secret |
從 取得的用戶端密鑰。 |
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'})
小茹
如要以程式輔助方式撤銷權杖,請向 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
和錯誤代碼。
導入跨帳戶防護
您還可以透過 Google 的跨帳戶保護服務,實施跨帳戶保護機制,進一步保護使用者的帳戶。這項服務可讓您訂閱安全性事件通知,為應用程式提供有關使用者帳戶重大變更的資訊。接著,您可以根據決定如何回應事件,使用這些資訊採取行動。
Google 跨帳戶保護服務傳送至應用程式的事件類型示例包括:
-
https://schemas.openid.net/secevent/risc/event-type/sessions-revoked
-
https://schemas.openid.net/secevent/oauth/event-type/token-revoked
-
https://schemas.openid.net/secevent/risc/event-type/account-disabled
如要進一步瞭解如何實作跨帳戶保護功能,以及查看可用的完整事件清單,請參閱「 透過跨帳戶保護功能保護使用者帳戶 」一文。