將 OAuth 2.0 用於網路伺服器應用程式

本文件說明網路伺服器應用程式如何使用 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 Live Streaming API 不支援服務帳戶流程。將服務帳戶無法連結至 YouTube 帳戶,因此嘗試透過此流程授權要求將產生 NoLinkedYouTubeAccount 錯誤。

用戶端程式庫

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

使用 Google API 用戶端程式庫處理應用程式 OAuth 2.0 流程時,用戶端程式庫會執行應用程式本身需要自行處理的許多動作。舉例來說,代碼可決定應用程式何時可以使用或重新整理儲存的存取權杖,以及應用程式必須取得同意聲明的時間。用戶端程式庫也會產生正確的重新導向網址,並協助導入重新導向處理常式,以交換存取權杖的授權碼。

適用於伺服器端應用程式的 Google API 用戶端程式庫支援下列語言:

先備知識

為專案啟用 API

凡是呼叫 Google API 的應用程式,都必須在 API Console中啟用這些 API。

如何在專案中啟用 API:

  1. Google API Console中的Open the API Library
  2. If prompted, select a project, or create a new one.
  3. 在「資料庫」頁面中,找出並啟用 YouTube Data API。找出應用程式會使用的任何其他 API,並啟用這些 API。

建立授權憑證

凡是使用 OAuth 2.0 存取 Google API 的應用程式,都必須擁有授權憑證,可供 Google 的 OAuth 2.0 伺服器識別該應用程式。下列步驟說明如何為專案建立憑證。這樣一來,應用程式即可使用憑證存取您為專案啟用的 API。

  1. Go to the Credentials page.
  2. 按一下 [Create credentials] (建立憑證) > [OAuth client ID] (OAuth 用戶端 ID)
  3. 選取「網頁應用程式」應用程式類型。
  4. 填寫表單,然後按一下「建立」。如果應用程式使用 PHP、Java、Python、Ruby 和 .NET 等語言與架構,就必須指定授權的重新導向 URI。重新導向 URI 是 OAuth 2.0 伺服器可傳送回應的端點。這些端點必須遵循 Google 的驗證規則

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

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

建立憑證後,請從 API Console下載 client_secret.json 檔案。請將檔案安全地儲存在只有您的應用程式可存取的位置。

識別存取權範圍

範圍可讓應用程式僅要求存取其所需的資源,也能讓使用者控管他們授予應用程式的存取權量。因此,要求的範圍數量與取得使用者同意聲明的可能性可能存在相反關係。

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

另外,應用程式也會透過漸進式授權程序要求授權範圍的存取權,您的應用程式會在相關情境下要求存取使用者資料。這項最佳做法可讓使用者輕鬆瞭解應用程式需要其要求存取權的原因。

YouTube Data API 第 3 版使用下列範圍:

狙擊鏡
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 程式碼範例,您需要:

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

    composer require google/apiclient:^2.10

Python

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

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

小茹

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

  • Ruby 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 要求。

以下清單簡要說明這些步驟:

  1. 您的應用程式會識別需要的權限。
  2. 您的應用程式會將使用者重新導向至 Google,以及所要求的權限清單。
  3. 使用者決定是否授予應用程式權限。
  4. 應用程式瞭解使用者決定的內容。
  5. 如果使用者已授予要求的權限,應用程式就會擷取代表使用者提出 API 要求所需的權杖。

步驟 1:設定授權參數

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

  • 如果您使用 Google 用戶端程式庫進行 OAuth 2.0 驗證和授權,請建立並設定定義這些參數的物件。
  • 如果您直接呼叫 Google OAuth 2.0 端點,則系統會產生網址並在該網址設定參數。

以下標籤定義網路伺服器應用程式支援的授權參數。特定語言的範例也會示範如何使用用戶端程式庫或授權程式庫,設定可設定這些參數的物件。

PHP

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

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

舉例來說,如要要求離線存取使用者 YouTube 資料,請按照下列步驟操作:

$client = new Google\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_typeinclude_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 'google/apis/youtube_v3'
require "googleauth"
require 'googleauth/stores/redis_token_store'

client_id = Google::Auth::ClientId.from_file('/path/to/client_secret.json')
scope = 'https://www.googleapis.com/auth/youtube.force-ssl'
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, '/oauth2callback')

您的應用程式使用用戶端物件執行 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 read-only Drive activity.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly'
];

// 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 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。您可以在 API Console Credentials page中找到這個值。

redirect_uri 必要

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

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

response_type 必要

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

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

scope 必要

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

範圍可讓應用程式僅要求存取所需的資源,也能讓使用者控管他們授予應用程式的存取權量。因此,要求的範圍數量與取得使用者同意聲明的可能性之間存在相反關係。

YouTube Data API 第 3 版使用下列範圍:

狙擊鏡
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。

建議您讓應用程式盡可能在情境中要求授權範圍的存取權。透過漸進式授權在情境下要求存取使用者資料,可讓使用者輕鬆瞭解應用程式需要要求存取權的原因。

access_type 建議採用

指出使用者未出現在瀏覽器中時,應用程式是否可以重新整理存取權杖。有效的參數值為 online (預設值) 和 offline

如果您的應用程式需要在使用者未顯示於瀏覽器時重新整理存取權杖,請將值設為 offline。這個方法可用來重新整理存取權杖,詳情請參閱本文後續章節。這個值會指示 Google 授權伺服器在第一次您的應用程式交換權杖授權碼時,傳回更新權杖「以及」存取權杖。

state 建議採用

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

您可以將這個參數用於多種用途,例如將使用者導向至應用程式中的正確資源、傳送 Nonce,以及降低跨網站要求的偽造情形。由於 redirect_uri 可以猜測,因此使用 state 值可讓您保證傳入連線是因驗證要求而產生的結果。如果您產生隨機字串或對 Cookie 雜湊或其他擷取用戶端狀態的值進行編碼,則您可以驗證回應,另外確保要求和回應源自相同的瀏覽器,提供防範跨網站要求偽造這類攻擊的防護措施。如需如何建立並確認 state 權杖的範例,請參閱 OpenID Connect 說明文件。

include_granted_scopes 選用

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

login_hint 選用

如果應用程式知道想要驗證的使用者,可使用此參數來提示 Google 驗證伺服器。伺服器會在登入表單中預先填入電子郵件欄位,或選取適當的多登入工作階段,並根據提示簡化登入流程。

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

prompt 選用

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

可能的值為:

none 不要顯示任何驗證或同意畫面。請勿使用其他值指定。
consent 提示使用者表示同意。
select_account 提示使用者選取帳戶。

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

將使用者重新導向至 Google 的 OAuth 2.0 伺服器,以便啟動驗證和授權程序。一般而言,如果應用程式首次需要存取使用者的資料,就會發生這種情形。以漸進式授權來說,當應用程式首次需要存取尚未取得權限的其他資源時,也會執行這個步驟。

PHP

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

Python

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

return flask.redirect(authorization_url)

小茹

  1. 產生網址以向 Google 的 OAuth 2.0 伺服器要求存取權:
    auth_uri = authorizer.get_authorization_url(login_hint: user_id, request: request)
  2. 將使用者重新導向至 auth_uri

Node.js

  1. 使用步驟 1 generateAuthUrl 方法中產生的網址 authorizationUrl,向 Google 的 OAuth 2.0 伺服器要求存取權。
  2. 將使用者重新導向至 authorizationUrl
    res.redirect(authorizationUrl);

HTTP/REST

Sample redirect to Google's authorization server

An example URL is shown below, with line breaks and spaces for readability. The URL requests access to a scope that permits access to retrieve the user's YouTube data. It uses incremental authorization (include_granted_scopes=true) to ensure that the new access token covers any scopes to which the user previously granted the application access. Several other parameters are also set in the example.

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.force-ssl&
 access_type=offline&
 include_granted_scopes=true&
 response_type=code&
 state=state_parameter_passthrough_value&
 redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback&
 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 帳戶無法授權一或多個要求的範圍,因此無法授權給這個 Google Workspace 管理員的政策。請參閱 Google Workspace 管理員說明文章「 控管哪些第三方應用程式和內部應用程式可存取 Google Workspace 資料」一文,進一步瞭解系統管理員如何限制所有範圍,或是敏感和受限制範圍的存取權,直到 OAuth 用戶端 ID 明確授予存取權為止。

disallowed_useragent

授權端點會顯示在 Google 的 OAuth 2.0 政策禁止的內嵌使用者代理程式中。

Android

android.webkit.WebView 中開啟授權要求時,Android 開發人員可能會看到這則錯誤訊息。開發人員應改用 Android 程式庫,例如 Android 專用 Google 登入或 OpenID Foundation 的 Android 適用的 AppAuth

當 Android 應用程式透過內嵌的使用者代理程式開啟一般網頁連結,且使用者從您的網站前往 Google 的 OAuth 2.0 授權端點時,網頁開發人員可能就會發生這個錯誤。開發人員應允許在作業系統的預設連結處理常式 (包括 Android 應用程式連結處理常式或預設的瀏覽器應用程式) 中開啟一般連結。您也可以使用 Android 自訂分頁程式庫。

iOS

iOS 和 macOS 開發人員在 WKWebView 中開啟授權要求時,可能會發生這個錯誤。開發人員應改用 iOS 程式庫,例如 iOS 專用 Google 登入或 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 不符。查看 Google API Console Credentials page中已授權的重新導向 URI。

redirect_uri 參數可能是指 OAuth 外架構 (OOB) 流程,但該流程已淘汰且不再受支援。如要更新整合作業,請參閱遷移指南

invalid_request

您提出的要求發生問題。可能原因如下:

  • 要求的格式不正確
  • 要求中缺少必要參數
  • 這項要求使用了 Google 不支援的授權方法。確認 OAuth 整合功能使用建議的整合方法

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

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

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

錯誤回應:

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

授權碼回應:

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

OAuth 2.0 伺服器回應範例

如要測試這個流程,請點選下列範例網址,該網址會要求查看 Google 雲端硬碟中檔案中繼資料的唯讀存取權:

https://accounts.google.com/o/oauth2/v2/auth?
 scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.force-ssl&
 access_type=offline&
 include_granted_scopes=true&
 response_type=code&
 state=state_parameter_passthrough_value&
 redirect_uri=http%3A%2F%2Flocalhost%2Foauth2callback&
 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/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,
    'scopes': credentials.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 從 API Console Credentials page取得的用戶端 ID。
client_secret 從 API Console Credentials page取得的用戶端密鑰。
code 初始要求傳回的授權碼。
grant_type 根據 OAuth 2.0 規格所定義,這個欄位的值必須設為 authorization_code
redirect_uri 在 API Console Credentials page 中,為指定 client_id 列出的專案其中一個重新導向 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/youtube.force-ssl",
  "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}

錯誤

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

invalid_grant

提供的授權碼無效或格式錯誤。請重新啟動 OAuth 程序,要求新的代碼並再次顯示同意聲明。

呼叫 Google API

PHP

完成下列步驟,即可使用存取權杖呼叫 Google API:

  1. 如要將存取權杖套用至新的 Google\Client 物件 (例如,假設您將存取權杖儲存在使用者工作階段中),請使用 setAccessToken 方法:
    $client->setAccessToken($access_token);
  2. 為您要呼叫的 API 建立服務物件。如要建立服務物件,您可以向要呼叫的 API 的建構函式提供已授權的 Google\Client 物件。舉例來說,如要呼叫 YouTube Data API,請執行以下操作:
    $youtube = new Google_Service_YouTube($client);
  3. 使用服務物件提供的介面向 API 服務發出要求。舉例來說,如要使用 YouTube Data API 擷取已驗證使用者頻道的現場直播清單:
    $broadcasts = $youtube->liveBroadcasts->listLiveBroadcasts('id,snippet', [ 'mine' => true ]);

Python

取得存取權杖後,應用程式即可使用該權杖來代表指定使用者帳戶或服務帳戶,授權 API 要求。使用使用者專屬授權憑證為您要呼叫的 API 建立服務物件,然後使用該物件發出已授權的 API 要求。

  1. 為您要呼叫的 API 建立服務物件。如要建立服務物件,您可以使用該 API 的名稱、版本和使用者憑證呼叫 googleapiclient.discovery 程式庫的 build 方法: 舉例來說,呼叫第 3 版的 YouTube Data API:
    from googleapiclient.discovery import build
    
    youtube = build('youtube', 'v3', credentials=credentials)
  2. 使用服務物件提供的介面向 API 服務發出要求。舉例來說,如要使用 YouTube Data API 擷取已驗證使用者頻道的現場直播清單:
    broadcasts = youtube.liveBroadcasts().list(part='id,snippet', mine=True).execute()

小茹

取得存取權杖後,應用程式即可使用該權杖代表指定使用者帳戶或服務帳戶,提出 API 要求。使用使用者專屬授權憑證為您要呼叫的 API 建立服務物件,然後使用該物件發出已授權的 API 要求。

  1. 為您要呼叫的 API 建立服務物件。 舉例來說,如要呼叫第 3 版的 YouTube Data API:
    youtube = Google::Apis::YoutubeV3::YouTubeService.new
  2. 設定服務的憑證:
    youtube.authorization = credentials
  3. 使用服務物件提供的介面向 API 服務發出要求。舉例來說,如要使用 YouTube Data API 擷取已驗證使用者頻道的現場直播清單,請按照下列步驟操作:
    broadcasts = youtube.list_liveBroadcasts('id,snippet', mine: true)

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

broadcasts = youtube.list_liveBroadcasts('id,snippet', mine: true)

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 的呼叫 (例如呼叫 YouTube Live Streaming API 時)。

請注意,YouTube Live Streaming API 不支援服務帳戶流程。由於無法將服務帳戶連結至 YouTube 帳戶,因此嘗試透過此流程授權要求時,會產生 NoLinkedYouTubeAccount 錯誤。

如要試用所有 Google API 及查看其範圍,請前往 OAuth 2.0 Playground

HTTP GET 範例

使用 Authorization: Bearer HTTP 標頭呼叫 liveBroadcasts.list 端點 (YouTube Live Streaming API) 的呼叫可能如下所示。請注意,您必須指定自己的存取權杖:

GET /youtube/v3/liveBroadcasts?part=id%2Csnippet&mine=true HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer access_token

以下示範如何使用 access_token 查詢字串參數,為通過驗證的使用者呼叫相同的 API:

GET https://www.googleapis.com/youtube/v3/liveBroadcasts?access_token=access_token&part=id%2Csnippet&mine=true

curl 個樣本

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

curl -H "Authorization: Bearer access_token" https://www.googleapis.com/youtube/v3/liveBroadcasts?part=id%2Csnippet&mine=true

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

curl https://www.googleapis.com/youtube/v3/liveBroadcasts?access_token=access_token&part=id%2Csnippet&mine=true

完整範例

以下範例顯示 JSON 格式物件,在使用者授權應用程式擷取該資料後,顯示已驗證使用者 YouTube 頻道的現場直播。

PHP

如要執行這個範例:

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

index.php

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

session_start();

$client = new Google\Client();
$client->setAuthConfig('client_secrets.json');
$client->addScope(Google_Service_YouTube::YOUTUBE_FORCE_SSL);

if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
  $client->setAccessToken($_SESSION['access_token']);
  $youtube = new Google_Service_YouTube($client);
  $broadcasts = $youtube->liveBroadcasts->listLiveBroadcasts('id,snippet', [ 'mine' => true ]);
  echo json_encode($broadcasts);
} 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_YouTube::YOUTUBE_FORCE_SSL);

if (! isset($_GET['code'])) {
  // Generate and set state value
  $state = bin2hex(random_bytes(16));
  $client->setState($state);
  $_SESSION['state'] = $state;

  $auth_url = $client->createAuthUrl();
  header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
} else {
  // Check the state value
  if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['state']) {
    die('State mismatch. Possible CSRF attack.');
  }
  $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,查看使用者撤銷授予應用程式的權限,且應用程式仍在嘗試以已撤銷的存取權權杖授權要求時,應用程式會收到哪些 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/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)

  broadcasts = youtube.liveBroadcasts().list(part='id,snippet', mine=True).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(**broadcasts)


@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)

小茹

本範例使用 Sinatra 架構。

require 'google/apis/youtube_v3'
require 'sinatra'
require 'googleauth'
require 'googleauth/stores/redis_token_store'

configure do
  enable :sessions

  set :client_id, Google::Auth::ClientId.from_file('/path/to/client_secret.json')
  set :scope, Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY
  set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new)
  set :authorizer, Google::Auth::WebUserAuthorizer.new(settings.client_id, settings.scope, settings.token_store, '/oauth2callback')
end

get '/' do
  user_id = settings.client_id.id
  credentials = settings.authorizer.get_credentials(user_id, request)
  if credentials.nil?
    redirect settings.authorizer.get_authorization_url(login_hint: user_id, request: request)
  end
  youtube = Google::Apis::YoutubeV3::YouTubeService.new
  broadcasts = youtube.list_liveBroadcasts('id,snippet', mine: true)
  
  "<pre>#{JSON.pretty_generate(broadcasts.to_h)}</pre>"
end

get '/oauth2callback' do
  target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request)
  redirect target_url
end

Node.js

如要執行這個範例:

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

main.js

const http = require('http');
const https = require('https');
const url = require('url');
const { google } = require('googleapis');
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 read-only Drive activity.
const scopes = [
  'https://www.googleapis.com/auth/drive.metadata.readonly'
];
/* 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 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 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
  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(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/youtube.force-ssl'
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://youtube.googleapis.com/youtube/v3/liveBroadcasts'
    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
    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'}
    r = requests.post('https://oauth2.googleapis.com/token', data=data)
    flask.session['credentials'] = r.text
    return flask.redirect(flask.url_for('index'))


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

重新導向 URI 驗證規則

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

驗證規則
配置

重新導向 URI 必須使用 HTTPS 配置,而非純文字 HTTP。本機主機 URI (包括 localhost IP 位址 URI) 不在這項規則的涵蓋範圍內。

主機

主機不得為原始 IP 位址。本機主機 IP 位址不在這項規則範圍內。

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

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

    路徑

    重新導向 URI 不得包含路徑週遊 (也稱為目錄回溯追蹤),以 “/..”“\..” 或網址編碼表示。

    查詢

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

    Fragment

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

    字元 重新導向 URI 不得包含特定字元,包括:
    • 萬用字元字元 ('*')
    • 不可列印的 ASCII 字元
    • 百分比編碼無效 (任何百分比編碼未遵循網址編碼形式的百分比符號後接兩個十六進位數字)
    • 空值字元 (編碼的 NULL 字元,例如%00%C0%80)

    漸進式授權

    在 OAuth 2.0 通訊協定中,您的應用程式要求授權存取資源,這些資源由範圍識別。在您需要的時候,對資源提出授權要求是最佳的使用者體驗。為了啟用這個做法,Google 的授權伺服器支援漸進式授權。這項功能可讓您視需要要求範圍,如果使用者授予新範圍的權限,則會傳回授權碼。當使用者授予專案的所有範圍,這個權杖就會傳回授權碼。

    舉例來說,假設應用程式會擷取已驗證使用者的 YouTube 頻道資料,並且允許使用者透過特殊流程擷取 YouTube Analytics (分析) 資料。在這種情況下,應用程式可能會在登入時要求存取 https://www.googleapis.com/auth/youtube.force-ssl 範圍。不過,如果使用者嘗試存取自己頻道的 Analytics (分析) 資料,應用程式也可以要求存取 https://www.googleapis.com/auth/yt-analytics.readonly 範圍。

    如要實作漸進式授權,請完成一般要求存取權杖的流程,但請確認授權要求包含先前授予的範圍。透過這種做法,您的應用程式就不必管理多個存取權杖。

    下列規則適用於從漸進式授權取得的存取權杖:

    • 權杖可用於存取與納入新合併授權的任一範圍相對應的資源。
    • 當您使用更新權杖取得合併授權以取得存取權杖時,存取權杖代表合併的授權,可以用於回應中包含的任何 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 資料,以擷取使用者的 YouTube 資料。

    GET https://accounts.google.com/o/oauth2/v2/auth?
      scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube.force-ssl&
      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 to offline 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

    為了重新整理存取權杖,應用程式會將 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'})

    小茹

    如要透過程式撤銷權杖,請向 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 和錯誤代碼。