適用於電視和有限輸入裝置應用程式的 OAuth 2.0

本文說明如何實作 OAuth 2.0 授權,以便使用在電視、遊戲主機和印表機等裝置上執行的應用程式存取 Google API。更具體地說,這個流程是專為無法存取瀏覽器或輸入功能有限的裝置而設計。

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

由於使用這個流程的應用程式會發布至個別裝置,因此系統會假設應用程式無法保存密鑰。使用者使用應用程式或應用程式在背景執行時,這類權杖可以存取 Google API。

替代方案

如果您「只」想讓使用者透過 Google 帳戶登入,並使用 JWT ID 權杖取得基本使用者個人資料,請參閱「在電視和輸入裝置受限的裝置上登入」。

必要條件

為專案啟用 API

任何呼叫 Google API 的應用程式,都必須在 API 控制台中啟用這些 API。

如要為專案啟用 API,請按照下列步驟操作:

  1. 在 Google API 控制台中開啟 API 程式庫。
  2. 按照系統說明選取專案,或建立新專案。
  3. API 程式庫會列出所有可用的 API,並按照產品系列及熱門程度分組。如果清單中找不到您想啟用的 API,請使用搜尋功能,或在所屬產品系列中點選「View All」(查看全部)。
  4. 選取要啟用的 API,然後按一下「啟用」按鈕。
  5. 如果系統顯示提示,請啟用帳單功能。
  6. 如果系統顯示提示,請詳閱並接受 API 的《服務條款》。

建立授權憑證

任何使用 OAuth 2.0 存取 Google API 的應用程式,都必須具備授權憑證,向 Google 的 OAuth 2.0 伺服器識別應用程式。下列步驟說明如何為專案建立憑證。接著,應用程式就能使用憑證,存取您為該專案啟用的 API。

  1. 前往「客戶」頁面。
  2. 按一下「Create Client」(建立用戶端)。
  3. 選取「電視和受限制的輸入裝置」應用程式類型。
  4. 為 OAuth 2.0 用戶端命名,然後點選「建立」。

找出存取權範圍

範圍可讓應用程式只要求存取所需資源,同時讓使用者控管授予應用程式的存取權限。因此,要求的範圍數量與取得使用者同意的可能性之間,可能存在反向關係。

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

查看已安裝應用程式或裝置的「允許的範圍」清單。

取得 OAuth 2.0 存取權杖

即使應用程式是在輸入功能有限的裝置上執行,使用者仍須透過輸入功能較豐富的裝置,才能完成這項授權流程。流程包含下列步驟:

  1. 應用程式向 Google 授權伺服器傳送要求,指出應用程式將要求存取哪些範圍。
  2. 伺服器會傳回後續步驟中使用的多項資訊,例如裝置代碼和使用者代碼。
  3. 您顯示使用者可在其他裝置上輸入的資訊,以便授權應用程式。
  4. 應用程式開始輪詢 Google 的授權伺服器,判斷使用者是否已授權應用程式。
  5. 使用者切換到輸入功能更豐富的裝置,開啟網路瀏覽器,前往步驟 3 顯示的網址,並輸入步驟 3 顯示的代碼。接著,使用者可以授予 (或拒絕) 應用程式存取權。
  6. 輪詢要求的下一個回應會包含權杖,應用程式需要這些權杖,才能代表使用者授權要求。(如果使用者拒絕授予應用程式存取權,回應就不會包含權杖)。

下圖說明這項程序:

使用者在另一部有瀏覽器的裝置上登入

以下各節將詳細說明這些步驟。考量到裝置可能具備的功能和執行階段環境範圍,本文件中的範例使用 curl 指令列公用程式。這些範例可移植到各種語言和執行階段。

步驟 1:要求裝置和使用者代碼

在這個步驟中,裝置會傳送 HTTP POST 要求至 Google 授權伺服器 (https://oauth2.googleapis.com/device/code),識別應用程式以及應用程式要代表使用者存取的存取權範圍。您應使用 device_authorization_endpoint 中繼資料值,從導覽文件擷取這個網址。請加入下列 HTTP 要求參數:

參數
client_id 必要

應用程式的用戶端 ID。您可以在 Cloud 控制台的「Clients」(用戶端) 頁面找到這個值。

scope 必要

以空格分隔的範圍清單,用於識別應用程式可代表使用者存取的資源。Google 會根據這些值,向使用者顯示同意畫面。查看已安裝應用程式或裝置的允許範圍清單。

範圍可讓應用程式只要求存取所需資源,同時讓使用者控管授予應用程式的存取權。因此,要求的範圍數量與取得使用者同意的可能性成反比。

範例

以下程式碼片段為要求範例:

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

client_id=client_id&scope=email%20profile

以下範例顯示使用 curl 指令傳送相同要求:

curl -d "client_id=client_id&scope=email%20profile" \
     https://oauth2.googleapis.com/device/code

步驟 2:處理授權伺服器回應

授權伺服器會傳回下列其中一項回應:

成功回應

如果要求有效,回應會是包含下列屬性的 JSON 物件:

屬性
device_code Google 指派的專屬值,用於識別執行應用程式並要求授權的裝置。使用者會透過另一部輸入功能更豐富的裝置,授權該裝置。舉例來說,使用者可能會使用筆電或手機,授權在電視上執行的應用程式。在本例中,device_code 代表電視。

這段程式碼可讓執行應用程式的裝置安全地判斷使用者是否已授予或拒絕存取權。

expires_in device_code 和 user_code 的有效時間長度 (以秒為單位)。如果使用者未在這段時間內完成授權流程,且裝置也未輪詢擷取使用者決定的相關資訊,您可能需要從步驟 1 重新啟動這項程序。
interval 裝置在輪詢要求之間應等待的時間長度 (以秒為單位)。舉例來說,如果值為 5,裝置應每五秒向 Google 的授權伺服器傳送一次輪詢要求。詳情請參閱步驟 3。
user_code 這個值會區分大小寫,用於向 Google 識別應用程式要求存取的範圍。使用者介面會指示使用者在輸入功能更豐富的其他裝置上輸入這個值。Google 接著會使用這個值,在提示使用者授予應用程式存取權時,顯示正確的範圍組合。
verification_url 使用者必須在其他裝置上前往的網址,以便輸入 user_code,並授予或拒絕應用程式存取權。使用者介面也會顯示這個值。

以下程式碼片段為回應範例:

{
  "device_code": "4/4-GMMhmHCXhWEzkobqIHGG_EnNYYsAkukHspeYUk9E8",
  "user_code": "GQVQ-JKEC",
  "verification_url": "https://www.google.com/device",
  "expires_in": 1800,
  "interval": 5
}

超過配額回應

如果裝置代碼要求超過與用戶端 ID 相關聯的配額,您會收到 403 回應,其中包含下列錯誤:

{
  "error_code": "rate_limit_exceeded"
}

在這種情況下,請使用輪詢策略降低要求頻率。

步驟 3:顯示使用者代碼

向使用者顯示在步驟 2 取得的 verification_url 和 user_code。這兩個值可以包含 US-ASCII 字元集中的任何可列印字元。向使用者顯示的內容應指示使用者在其他裝置上前往 verification_url,並輸入 user_code。

設計使用者介面 (UI) 時,請留意下列規則:

  • user_code
    • user_code 必須顯示在可容納 15 個「W」大小字元的欄位中。換句話說,如果您能正確顯示 WWWWWWWWWWWWWWW 程式碼,代表 UI 有效,建議您在測試 user_code 在 UI 中的顯示方式時,使用該字串值。
    • user_code 會區分大小寫,請勿以任何方式修改,例如變更大小寫或插入其他格式字元。
  • verification_url
    • 顯示 verification_url 的空間必須夠寬,才能容納長度為 40 個字元的網址字串。
    • 請勿以任何方式修改 verification_url,但可視需要移除顯示的配置。如果您打算基於顯示原因從網址中移除配置 (例如 https://),請務必確保應用程式可以處理 http 和 https 變體。

步驟 4:輪詢 Google 的授權伺服器

由於使用者會使用其他裝置前往 verification_url 並授予 (或拒絕) 存取權,因此當使用者回應存取要求時,要求裝置不會自動收到通知。因此,要求裝置必須輪詢 Google 的授權伺服器,判斷使用者何時回應要求。

要求裝置應持續傳送輪詢要求,直到收到回應,指出使用者已回應存取要求,或 步驟 2 中取得的 device_code 和 user_code 已過期。步驟 2 傳回的 interval 會指定要求之間的等待時間 (以秒為單位)。

要輪詢的端點網址為 https://oauth2.googleapis.com/token。輪詢要求包含下列參數:

參數
client_id 應用程式的用戶端 ID。您可以在 Cloud 控制台的「Clients」(用戶端) 頁面找到這個值。
client_secret 所提供 client_id 的用戶端密鑰。您可以在 Cloud 控制台的「Clients」(用戶端) 頁面找到這個值。
device_code 授權伺服器在步驟 2 傳回的 device_code。
grant_type 請將這個值設為 urn:ietf:params:oauth:grant-type:device_code。

範例

以下程式碼片段為要求範例:

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

client_id=client_id&
client_secret=client_secret&
device_code=device_code&
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code

以下範例顯示使用 curl 指令傳送相同要求:

curl -d "client_id=client_id&client_secret=client_secret& \
         device_code=device_code& \
         grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code" \
         -H "Content-Type: application/x-www-form-urlencoded" \
         https://oauth2.googleapis.com/token

步驟 5:使用者回應存取權要求

下圖顯示使用者前往您在步驟 3 中顯示的 verification_url 時,會看到的類似頁面:

輸入代碼連結裝置

輸入 user_code 並登入 Google (如果尚未登入) 後,使用者會看到同意畫面:

裝置用戶端的同意畫面範例

步驟 6:處理輪詢要求的回應

Google 的授權伺服器會以下列其中一種回應,回覆每個輪詢要求:

已授予存取權

如果使用者授予裝置存取權 (在同意畫面中點選「Allow」),回應就會包含存取權杖和更新權杖。裝置可使用這些權杖,代表使用者存取 Google API。(回應中的 scope 屬性會決定裝置可存取的 API)。

在這種情況下,API 回應會包含下列欄位:

欄位
access_token 應用程式傳送的權杖,用於授權 Google API 要求。
expires_in 存取權杖的剩餘生命週期 (以秒為單位)。
refresh_token 可用來取得新存取權杖的權杖。更新權杖在使用者撤銷存取權或更新權杖到期前都有效。請注意,系統一律會傳回裝置的更新權杖。
scope access_token 授予的存取權範圍,以空格分隔、區分大小寫的字串清單表示。
token_type 傳回的權杖類型。這個值一律為 Bearer。

以下程式碼片段為回應範例:

{
  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "scope": "openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email",
  "token_type": "Bearer",
  "refresh_token": "1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}

存取權杖的效期有限。如果應用程式需要長期存取 API,可以使用更新權杖取得新的存取權杖。如果應用程式需要這類存取權,則應儲存更新權杖,以供日後使用。

存取遭拒

如果使用者拒絕授予裝置存取權,伺服器回應會包含 403 HTTP 回應狀態碼 (Forbidden)。回應包含下列錯誤:

{
  "error": "access_denied",
  "error_description": "Forbidden"
}

待授權

如果使用者尚未完成授權流程,伺服器會傳回 428 HTTP 回應狀態碼 (Precondition Required)。回應包含下列錯誤:

{
  "error": "authorization_pending",
  "error_description": "Precondition Required"
}

輪詢頻率過高

如果裝置傳送輪詢要求的頻率過高,伺服器會傳回 403 HTTP 回應狀態碼 (Forbidden)。回應包含下列錯誤:

{
  "error": "slow_down",
  "error_description": "Forbidden"
}

其他錯誤

如果輪詢要求缺少任何必要參數或參數值不正確,授權伺服器也會傳回錯誤。這類要求通常會傳回 400 (Bad Request) 或 401 (Unauthorized) HTTP 回應狀態碼。這些錯誤包括:

錯誤 HTTP 狀態碼 說明
admin_policy_enforced 400 根據 Google Workspace 管理員的政策,Google 帳戶無法授權一或多個要求的範圍。如要進一步瞭解管理員如何限制範圍存取權,直到明確授予 OAuth 用戶端 ID 存取權為止,請參閱 Google Workspace 管理員說明文章「控管哪些第三方和內部應用程式可存取 Google Workspace 資料」。
invalid_client 401

找不到 OAuth 用戶端。舉例來說,如果 client_id 參數值無效,就會發生這項錯誤。

OAuth 用戶端類型不正確。確認用戶端 ID 的應用程式類型設為「電視和受限制的輸入裝置」。

invalid_grant 400 code 參數值無效、已有人聲明擁有權,或無法剖析。
unsupported_grant_type 400 grant_type 參數值無效。
org_internal 403 要求中的 OAuth 用戶端 ID 屬於某個專案,該專案限制只有特定 Google Cloud 組織中的 Google 帳戶才能存取。確認 OAuth 應用程式的使用者類型設定。

呼叫 Google API

應用程式取得存取權杖後,如果已獲得 API 所需的存取範圍,您就能使用權杖,代表特定使用者帳戶呼叫 Google API。如要這麼做,請在向 API 提出的要求中加入 access_token 查詢參數或 Authorization HTTP 標頭 Bearer 值,藉此加入存取權杖。建議您盡可能使用 HTTP 標頭,因為查詢字串通常會顯示在伺服器記錄中。在大多數情況下,您可以使用用戶端程式庫設定對 Google API 的呼叫 (例如呼叫 Drive Files API)。

您可以在 OAuth 2.0 Playground 試用所有 Google API 並查看其範圍。

HTTP GET 範例

使用 Authorization: Bearer HTTP 標頭呼叫 drive.files 端點 (Drive Files API) 的範例如下。請注意,您必須指定自己的存取權杖:

GET /drive/v2/files HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer access_token

以下是使用 access_token 查詢字串參數,為已驗證使用者呼叫相同 API 的範例:

GET https://www.googleapis.com/drive/v2/files?access_token=access_token

curl 範例

您可以使用 curl 指令列應用程式測試這些指令。以下是使用 HTTP 標頭選項 (建議) 的範例:

curl -H "Authorization: Bearer access_token" https://www.googleapis.com/drive/v2/files

或者,您也可以使用查詢字串參數選項:

curl https://www.googleapis.com/drive/v2/files?access_token=access_token

重新整理存取權杖

存取權杖會定期過期,並成為相關 API 要求的無效憑證。如果您要求離線存取與權杖相關聯的範圍,則可以重新整理存取權杖,而不提示使用者授予權限 (包括使用者不在場時)。

如要重新整理存取權杖,應用程式會向 Google 的授權伺服器 (https://oauth2.googleapis.com/token) 傳送 HTTPS POST 要求,並在要求主體中加入下列參數:

名稱 值
client_id 從 API 控制台取得的用戶端 ID。
client_secret 選填

從 API 控制台取得的用戶端密鑰。

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&
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 https://www.googleapis.com/auth/calendar.readonly",
  "token_type": "Bearer"
}

請注意,系統會限制核發的更新權杖數量,包括每個用戶端/使用者組合的限制,以及每位使用者在所有用戶端中的限制。請將更新權杖儲存在長期儲存空間,並在有效期間內持續使用。如果應用程式要求的更新權杖過多,可能會達到這些限制,導致舊的更新權杖失效。

撤銷權杖

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

應用程式也可以透過程式輔助方式撤銷已獲得的存取權。如果使用者取消訂閱、移除應用程式,或應用程式所需的 API 資源大幅變更,則程式輔助撤銷功能就相當重要。換句話說,移除程序的一部分可能包括 API 要求,以確保先前授予應用程式的權限已移除。

如要透過程式輔助方式撤銷權杖,應用程式會向 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 和錯誤代碼。

允許的範圍

裝置的 OAuth 2.0 流程僅支援下列範圍:

OpenID Connect、Google 登入

  • email
  • openid
  • profile

Drive API

  • https://www.googleapis.com/auth/drive.appdata
  • https://www.googleapis.com/auth/drive.file

YouTube API

  • https://www.googleapis.com/auth/youtube
  • https://www.googleapis.com/auth/youtube.readonly

以時間為依據的存取權

使用者可透過限時存取權,授予應用程式在有限時間內存取資料的權限,以便完成操作。在同意聲明流程中,部分 Google 產品提供限時存取權,使用者可以選擇授予限時存取權。例如 Data Portability API,可一次性轉移資料。

使用者授予應用程式限時存取權後,更新權杖會在指定時間過後失效。請注意,在特定情況下,更新權杖可能會提早失效,詳情請參閱這些案例。在這種情況下,授權碼交換回應中傳回的 refresh_token_expires_in 欄位,代表更新權杖到期前的剩餘時間。

導入跨帳戶防護功能

為保護使用者帳戶,您應採取的額外步驟是使用 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

如要進一步瞭解如何實作跨帳戶防護,以及查看可用事件的完整清單,請參閱「 使用跨帳戶防護保護使用者帳戶 」頁面。