本文說明如何實作 OAuth 2.0 授權,以便從 JavaScript 網頁應用程式存取 Google API。OAuth 2.0 可讓使用者與應用程式共用特定資料,同時保有使用者名稱、密碼和其他資訊的隱私。舉例來說,應用程式可以使用 OAuth 2.0 取得使用者權限,將檔案儲存在使用者的 Google 雲端硬碟中。
這個 OAuth 2.0 流程稱為隱含授權流程。適用於僅在使用者使用應用程式時存取 API 的應用程式。這類應用程式無法儲存機密資訊。
在這個流程中,應用程式會開啟 Google 網址,並使用查詢參數來識別應用程式和應用程式所需的 API 存取權類型。您可以在目前的瀏覽器視窗或對話方塊中開啟網址。使用者可以透過 Google 驗證身分,並授予要求的權限。 接著,Google 會將使用者重新導向至您的應用程式。重新導向作業包含存取權杖,您的應用程式會驗證該權杖,然後使用該權杖提出 API 要求。
Google API 用戶端程式庫和 Google Identity 服務
如果您使用適用於 JavaScript 的 Google API 用戶端程式庫,向 Google 發出授權呼叫,則應使用 Google Identity 服務 JavaScript 程式庫處理授權流程。強烈建議您使用 Google Identity 服務 程式碼模型,該模型是以更安全的授權碼流程搭配 PKCE 為基礎。
必要條件
為專案啟用 API
任何呼叫 Google API 的應用程式,都必須在 API 控制台中啟用這些 API。
如要為專案啟用 API,請按照下列步驟操作:
- 在 Google API 控制台中開啟 API 程式庫。
- 按照系統說明選取專案,或建立新專案。
- API 程式庫會列出所有可用的 API,並按照產品系列及熱門程度分組。如果清單中找不到您想啟用的 API,請使用搜尋功能,或在所屬產品系列中點選「View All」(查看全部)。
- 選取要啟用的 API,然後按一下「啟用」按鈕。
- 如果系統顯示提示,請啟用帳單功能。
- 如果系統顯示提示,請詳閱並接受 API 的《服務條款》。
建立授權憑證
任何使用 OAuth 2.0 存取 Google API 的應用程式,都必須具備授權憑證,向 Google 的 OAuth 2.0 伺服器識別應用程式。下列步驟說明如何為專案建立憑證。接著,應用程式就能使用憑證,存取您為該專案啟用的 API。
- 前往「客戶」頁面。
- 按一下「Create Client」(建立用戶端)。
- 選取「網頁應用程式」類型。
- 填妥表單。如果應用程式使用 JavaScript 發出已授權的 Google API 要求,就必須指定已授權的 JavaScript 來源。來源會識別應用程式可從哪些網域傳送要求至 OAuth 2.0 伺服器。這些來源必須遵守 Google 的驗證規則。
找出存取權範圍
範圍可讓應用程式只要求存取所需資源,同時讓使用者控管授予應用程式的存取權限。因此,要求的範圍數量與取得使用者同意的可能性之間,可能存在反向關係。
開始實作 OAuth 2.0 授權前,建議您先找出應用程式需要存取權限的範圍。
OAuth 2.0 API 範圍文件包含您可能用於存取 Google API 的完整範圍清單。
取得 OAuth 2.0 存取權杖
下列步驟說明應用程式如何與 Google 的 OAuth 2.0 伺服器互動,取得使用者同意,代表使用者執行 API 要求。應用程式必須先取得這項同意聲明,才能執行需要使用者授權的 Google API 要求。
步驟 1:重新導向至 Google 的 OAuth 2.0 伺服器
如要要求存取使用者資料的權限,請將使用者重新導向至 Google 的 OAuth 2.0 伺服器。
OAuth 2.0 端點
產生網址,向 Google 的 OAuth 2.0 端點 (https://accounts.google.com/o/oauth2/v2/auth) 要求存取權。這個端點可透過 HTTPS 存取,但會拒絕一般 HTTP 連線。
Google 授權伺服器支援下列網路伺服器應用程式查詢字串參數:
| 參數 | |||||||
|---|---|---|---|---|---|---|---|
client_id |
必要
應用程式的用戶端 ID。您可以在 Cloud 控制台的「Clients」(用戶端) 頁面找到這個值。 |
||||||
redirect_uri |
必要
決定使用者完成授權流程後,API 伺服器會將使用者重新導向至何處。這個值必須與 OAuth 2.0 用戶端的其中一個已授權重新導向 URI 完全相符,您可以在用戶端的 Cloud 控制台「Clients」(用戶端) 頁面設定。如果這個值與所提供 請注意, |
||||||
response_type |
必要
JavaScript 應用程式必須將參數值設為 |
||||||
scope |
必要
以空格分隔的範圍清單,用於識別應用程式可代表使用者存取的資源。Google 會根據這些值,向使用者顯示同意畫面。 範圍可讓應用程式只要求存取所需資源,同時讓使用者控管授予應用程式的存取權。因此,要求的範圍數量與取得使用者同意的可能性成反比。 建議您盡可能讓應用程式在相關情境中要求授權範圍存取權。在相關情境中,使用漸進式授權要求存取使用者資料,可協助使用者瞭解應用程式為何需要所要求的存取權。 |
||||||
state |
建議
指定應用程式用來在授權要求和授權伺服器回應之間維持狀態的任何字串值。使用者同意或拒絕應用程式的存取要求後,伺服器會傳回您在 這個參數有多種用途,例如將使用者導向應用程式中的正確資源、傳送 Nonce,以及防範跨網站偽造要求。由於您的 |
||||||
include_granted_scopes |
選填
可讓應用程式使用漸進式授權,在情境中要求存取其他範圍。如果將這個參數的值設為 |
||||||
enable_granular_consent |
選填
預設為 Google 啟用應用程式的精細權限後,這項參數就不會再有任何作用。 |
||||||
login_hint |
選填
如果應用程式知道嘗試驗證的使用者身分,就可以使用這個參數,向 Google 驗證伺服器提供提示。伺服器會使用提示,在登入表單中預先填入電子郵件欄位,或選取適當的多重登入工作階段,簡化登入流程。 將參數值設為電子郵件地址或 |
||||||
prompt |
選填
以空格分隔、區分大小寫的提示清單,用於向使用者顯示。如未指定這個參數,系統只會在專案首次要求存取權時提示使用者。詳情請參閱「 提示重新同意」。 可能的值為:
|
||||||
重新導向至 Google 授權伺服器的範例
以下是網址範例,其中包含換行和空格,方便閱讀。
https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly& include_granted_scopes=true& response_type=token& state=state_parameter_passthrough_value& redirect_uri=https%3A//developers.google.com/oauthplayground& client_id=client_id
建立要求網址後,請將使用者重新導向至該網址。
JavaScript 程式碼範例
下列 JavaScript 程式碼片段說明如何在 JavaScript 中啟動授權流程,而不使用 Google API 用戶端程式庫 for JavaScript。由於這個 OAuth 2.0 端點不支援跨源資源共享 (CORS),因此程式碼片段會建立表單,開啟對該端點的要求。
/* * Create form to request access token from Google's OAuth 2.0 server. */ function oauthSignIn() { // Google's OAuth 2.0 endpoint for requesting an access token var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth'; // Create <form> element to submit parameters to OAuth 2.0 endpoint. var form = document.createElement('form'); form.setAttribute('method', 'GET'); // Send as a GET request. form.setAttribute('action', oauth2Endpoint); // Parameters to pass to OAuth 2.0 endpoint. var params = {'client_id': 'YOUR_CLIENT_ID', 'redirect_uri': 'YOUR_REDIRECT_URI', 'response_type': 'token', 'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly', 'include_granted_scopes': 'true', 'state': 'pass-through value'}; // Add form parameters as hidden input values. for (var p in params) { var input = document.createElement('input'); input.setAttribute('type', 'hidden'); input.setAttribute('name', p); input.setAttribute('value', params[p]); form.appendChild(input); } // Add form to page and submit it to open the OAuth 2.0 endpoint. document.body.appendChild(form); form.submit(); }
步驟 2:Google 提示使用者提供同意聲明
在這個步驟中,使用者會決定是否要授予應用程式要求的存取權。在這個階段,Google 會顯示同意聲明視窗,其中會列出應用程式名稱、應用程式要求存取的 Google API 服務 (需要使用者的授權憑證),以及要授予的存取權範圍摘要。使用者可以同意授予應用程式要求的一或多個範圍存取權,也可以拒絕要求。
應用程式在這個階段不需要執行任何動作,只要等待 Google 的 OAuth 2.0 伺服器回應,指出是否已授予任何存取權。我們會在下一個步驟中說明該回應。
錯誤
向 Google 的 OAuth 2.0 授權端點發出要求時,可能會顯示面向使用者的錯誤訊息,而非預期的驗證和授權流程。常見的錯誤代碼和建議解決方法如下:
admin_policy_enforced
由於 Google Workspace 管理員的政策,Google 帳戶無法授權一或多個要求的範圍。請參閱 Google Workspace 管理員說明文章「 控管哪些第三方和內部應用程式可存取 Google Workspace 資料」,進一步瞭解管理員如何限制所有範圍或機密和受限範圍的存取權,直到明確授予 OAuth 用戶端 ID 存取權為止。
disallowed_useragent
授權端點顯示在 Google OAuth 2.0 政策不允許的嵌入式使用者代理程式中。
iOS 和 macOS 開發人員在 WKWebView 中開啟授權要求時,可能會遇到這項錯誤。開發人員應改用 iOS 程式庫,例如 Google 登入 iOS 版或 OpenID Foundation 的 AppAuth iOS 版。
如果 iOS 或 macOS 應用程式在內嵌的使用者代理程式中開啟一般網頁連結,且使用者從您的網站前往 Google 的 OAuth 2.0 授權端點,網頁程式開發人員可能會遇到這項錯誤。開發人員應允許一般連結在作業系統的預設連結處理常式中開啟,包括通用連結處理常式或預設瀏覽器應用程式。SFSafariViewController 程式庫也是支援的選項。
org_internal
要求中的 OAuth 用戶端 ID 屬於某個專案,該專案限制只有特定 Google Cloud 組織中的 Google 帳戶才能存取。如要進一步瞭解這個設定選項,請參閱「設定 OAuth 同意畫面」說明文章中的「使用者類型」一節。
invalid_client
提出要求的來源未獲得這個用戶端的授權。請參閱 origin_mismatch。
deleted_client
用來提出要求的 OAuth 用戶端已刪除。刪除作業可以手動執行,也可以在用戶端未使用 時自動執行。刪除的用戶端可在刪除後的 30 天內還原。 瞭解詳情 。
invalid_grant
使用漸進式授權時,權杖可能已過期或失效。請再次驗證使用者,並徵求使用者同意取得新權杖。如果持續看到這則錯誤訊息,請確認應用程式設定正確無誤,且要求中使用的權杖和參數正確。否則,使用者帳戶可能已遭刪除或停用。
origin_mismatch
授權要求來源 JavaScript 的配置、網域和/或通訊埠,可能與 OAuth 用戶端 ID 註冊的授權 JavaScript 來源 URI 不符。在 Google Cloud 控制台的「Clients」(用戶端) 頁面中,查看已授權的 JavaScript 來源。
redirect_uri_mismatch
授權要求中傳遞的 redirect_uri 與 OAuth 用戶端 ID 的授權重新導向 URI 不符。在 Google Cloud 控制台的「Clients」(用戶端) 頁面中,查看已授權的重新導向 URI。
授權要求來源 JavaScript 的配置、網域和/或通訊埠,可能與 OAuth 用戶端 ID 註冊的授權 JavaScript 來源 URI 不符。在 Google Cloud 控制台的「Clients」(用戶端) 頁面,查看已授權的 JavaScript 來源。
redirect_uri 參數可能參照已淘汰且不再支援的 OAuth 頻外 (OOB) 流程。請參閱遷移指南,更新整合功能。
invalid_request
您的要求有誤。可能原因如下:
- 要求格式不正確
- 要求缺少必要參數
- 要求採用 Google 不支援的授權方法。確認 OAuth 整合採用建議的整合方法
步驟 3:處理 OAuth 2.0 伺服器回應
OAuth 2.0 端點
OAuth 2.0 伺服器會將回應傳送至存取權杖要求中指定的 redirect_uri。
如果使用者核准要求,回應會包含存取權杖。如果使用者未核准要求,回應會包含錯誤訊息。存取權杖或錯誤訊息會傳回至重新導向 URI 的雜湊片段,如以下範例所示:
存取權杖回應:
https://oauth2.example.com/callback#access_token=4/P7q7W91&token_type=Bearer&expires_in=3600
除了
access_token參數外,片段字串也包含token_type參數 (一律設為Bearer) 和expires_in參數 (指定權杖的生命週期,以秒為單位)。如果在存取權杖要求中指定state參數,回應中也會包含該參數的值。- 錯誤回應:
https://oauth2.example.com/callback#error=access_denied
OAuth 2.0 伺服器回應範例
您可以點選下列範例網址來測試這個流程,該網址會要求唯讀存取權,以便查看 Google 雲端硬碟中的檔案中繼資料,以及查看 Google 日曆活動:
https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly& include_granted_scopes=true& response_type=token& state=state_parameter_passthrough_value& redirect_uri=https%3A//developers.google.com/oauthplayground& client_id=client_id
完成 OAuth 2.0 流程後,瀏覽器會將您重新導向至 OAuth 2.0 Playground,這項工具可用於測試 OAuth 流程。您會看到 OAuth 2.0 Playground 已自動擷取授權碼。
步驟 4:查看使用者授予的範圍
要求多項權限 (範圍) 時,使用者可能不會授予應用程式所有權限。應用程式必須驗證實際授予的範圍,並妥善處理部分權限遭拒的情況,通常是停用依賴這些遭拒範圍的功能。
不過也有例外狀況。如果 Google Workspace Enterprise 應用程式具有全網域授權,或標示為可信任,就會略過精細權限同意畫面。使用者不會看到這類應用程式的精細權限同意畫面。而是會取得所有要求的範圍,或完全無法取得。
詳情請參閱「如何處理精細權限」。
OAuth 2.0 端點
如要檢查使用者是否已授予應用程式特定範圍的存取權,請查看存取權杖回應中的 scope 欄位。access_token 授予的存取權杖範圍,以空格分隔、區分大小寫的字串清單表示。
舉例來說,下列存取權杖回應範例表示使用者已授予應用程式唯讀雲端硬碟活動和日曆活動權限:
{ "access_token": "1/fFAGRNJru1FTz70BzhT3Zg", "expires_in": 3920, "token_type": "Bearer", "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly", "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" }
呼叫 Google API
OAuth 2.0 端點
應用程式取得存取權杖後,如果已獲得 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
JavaScript 程式碼範例
下列程式碼片段示範如何使用 CORS (跨源資源共享) 向 Google API 傳送要求。這個範例未使用 JavaScript 適用的 Google API 用戶端程式庫。不過,即使您不使用用戶端程式庫,該程式庫說明文件中的 CORS 支援指南,也可能有助於您進一步瞭解這些要求。
在這個程式碼片段中,access_token 變數代表您取得的權杖,用於代表授權使用者提出 API 要求。完整範例示範如何將該權杖儲存在瀏覽器的本機儲存空間,並在提出 API 要求時擷取。
var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://www.googleapis.com/drive/v3/about?fields=user&' + 'access_token=' + params['access_token']); xhr.onreadystatechange = function (e) { console.log(xhr.response); }; xhr.send(null);
完整範例
OAuth 2.0 端點
這個程式碼範例示範如何在不使用 Google API JavaScript 用戶端程式庫的情況下,以 JavaScript 完成 OAuth 2.0 流程。這段程式碼適用於 HTML 網頁,會顯示按鈕,讓您試用 API 要求。點選按鈕後,程式碼會檢查網頁是否已將 API 存取權杖儲存在瀏覽器的本機儲存空間。如果是,就會執行 API 要求。否則,系統會啟動 OAuth 2.0 流程。
OAuth 2.0 流程的步驟如下:
- 系統會將使用者導向 Google 的 OAuth 2.0 伺服器,該伺服器會要求存取
https://www.googleapis.com/auth/drive.metadata.readonly和https://www.googleapis.com/auth/calendar.readonly範圍。 - 使用者授予 (或拒絕) 一或多個要求範圍的存取權後,系統會將使用者重新導向至原始頁面,並從片段 ID 字串剖析存取權杖。
- 該頁面會檢查使用者已授予應用程式哪些範圍的存取權。
如果使用者已授予所要求範圍的存取權,頁面會使用存取權杖提出範例 API 要求。
API 要求會呼叫 Drive API 的
about.get方法,擷取授權使用者的 Google 雲端硬碟帳戶相關資訊。- 如果要求執行成功,API 回應會記錄在瀏覽器的偵錯控制台中。
您可以透過 Google 帳戶的「權限」頁面撤銷應用程式的存取權。建立用戶端 ID 時,應用程式會列為 OAuth 同意畫面中品牌宣傳頁面提供的應用程式名稱。
如要在本機執行這段程式碼,您必須為 YOUR_CLIENT_ID 和 YOUR_REDIRECT_URI 變數設定與授權憑證對應的值。YOUR_REDIRECT_URI 變數應設為與網頁放送網址相同的網址。這個值必須與 Google Cloud 控制台「用戶端」頁面中,OAuth 2.0 用戶端的其中一個已授權重新導向 URI 完全相符,如果這個值與授權 URI 不符,您會收到 redirect_uri_mismatch 錯誤。此外,專案也必須啟用這項要求的適當 API。
<html><head></head><body>
<script>
var YOUR_CLIENT_ID = 'REPLACE_THIS_VALUE';
var YOUR_REDIRECT_URI = 'REPLACE_THIS_VALUE';
// Parse query string to see if page request is coming from OAuth 2.0 server.
var fragmentString = location.hash.substring(1);
var params = {};
var regex = /([^&=]+)=([^&]*)/g, m;
while (m = regex.exec(fragmentString)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
if (Object.keys(params).length > 0 && params['state']) {
if (params['state'] == localStorage.getItem('state')) {
localStorage.setItem('oauth2-test-params', JSON.stringify(params) );
trySampleRequest();
} else {
console.log('State mismatch. Possible CSRF attack');
}
}
// Function to generate a random state value
function generateCryptoRandomState() {
const randomValues = new Uint32Array(2);
window.crypto.getRandomValues(randomValues);
// Encode as UTF-8
const utf8Encoder = new TextEncoder();
const utf8Array = utf8Encoder.encode(
String.fromCharCode.apply(null, randomValues)
);
// Base64 encode the UTF-8 data
return btoa(String.fromCharCode.apply(null, utf8Array))
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// If there's an access token, try an API request.
// Otherwise, start OAuth 2.0 flow.
function trySampleRequest() {
var params = JSON.parse(localStorage.getItem('oauth2-test-params'));
if (params && params['access_token']) {
// User authorized the request. Now, check which scopes were granted.
if (params['scope'].includes('https://www.googleapis.com/auth/drive.metadata.readonly')) {
// User authorized read-only Drive activity permission.
// Calling the APIs, etc.
var xhr = new XMLHttpRequest();
xhr.open('GET',
'https://www.googleapis.com/drive/v3/about?fields=user&' +
'access_token=' + params['access_token']);
xhr.onreadystatechange = function (e) {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr.response);
} else if (xhr.readyState === 4 && xhr.status === 401) {
// Token invalid, so prompt for user permission.
oauth2SignIn();
}
};
xhr.send(null);
}
else {
// User didn't authorize read-only Drive activity permission.
// Update UX and application accordingly
console.log('User did not authorize read-only Drive activity permission.');
}
// Check if user authorized Calendar read permission.
if (params['scope'].includes('https://www.googleapis.com/auth/calendar.readonly')) {
// User authorized Calendar read permission.
// Calling the APIs, etc.
console.log('User authorized Calendar read permission.');
}
else {
// User didn't authorize Calendar read permission.
// Update UX and application accordingly
console.log('User did not authorize Calendar read permission.');
}
} else {
oauth2SignIn();
}
}
/*
* Create form to request access token from Google's OAuth 2.0 server.
*/
function oauth2SignIn() {
// create random state value and store in local storage
var state = generateCryptoRandomState();
localStorage.setItem('state', state);
// Google's OAuth 2.0 endpoint for requesting an access token
var oauth2Endpoint = 'https://accounts.google.com/o/oauth2/v2/auth';
// Create element to open OAuth 2.0 endpoint in new window.
var form = document.createElement('form');
form.setAttribute('method', 'GET'); // Send as a GET request.
form.setAttribute('action', oauth2Endpoint);
// Parameters to pass to OAuth 2.0 endpoint.
var params = {'client_id': YOUR_CLIENT_ID,
'redirect_uri': YOUR_REDIRECT_URI,
'scope': 'https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly',
'state': state,
'include_granted_scopes': 'true',
'response_type': 'token'};
// Add form parameters as hidden input values.
for (var p in params) {
var input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', p);
input.setAttribute('value', params[p]);
form.appendChild(input);
}
// Add form to page and submit it to open the OAuth 2.0 endpoint.
document.body.appendChild(form);
form.submit();
}
</script>
<button onclick="trySampleRequest();">Try sample request</button>
</body></html>JavaScript 來源驗證規則
Google 會對 JavaScript 來源套用下列驗證規則,協助開發人員確保應用程式安全防護。JavaScript 來源必須遵守這些規則。 如需瞭解這些規則中使用的網域、主機和配置定義,請參閱 RFC 3986 第 3 節。
| 驗證規則 | |
|---|---|
| 配置 |
JavaScript 來源必須使用 HTTPS 配置,而非一般 HTTP。Localhost URI (包括 localhost IP 位址 URI) 不受這項規則限制。 |
| 主機 |
主機不得為原始 IP 位址。Localhost IP 位址不受這項規則限制。 |
| 網域 |
“googleusercontent.com”。goo.gl)。 |
| Userinfo |
JavaScript 來源不得包含 userinfo 子元件。 |
| 路徑 |
JavaScript 來源不得包含路徑元件。 |
| 查詢 |
JavaScript 來源不得包含查詢元件。 |
| Fragment |
JavaScript 來源不得包含片段元件。 |
| 角色 |
JavaScript 來源不得包含特定字元,包括:
|
增量授權
在 OAuth 2.0 通訊協定中,應用程式會要求授權存取資源,而資源是由範圍識別。在需要時才要求資源授權,是提供最佳使用者體驗的做法。為實現這項做法,Google 的授權伺服器支援增量授權。這項功能可讓您視需要要求範圍,如果使用者授予新範圍的權限,則會傳回授權碼,可用於交換包含使用者授予專案的所有範圍的權杖。
舉例來說,如果應用程式可讓使用者試聽音樂曲目及建立混音,則在登入時可能只需要極少資源,或許只要登入者的姓名即可。不過,如要儲存完成的混音,則需要存取他們的 Google 雲端硬碟。大多數使用者會認為,只有在應用程式實際需要存取 Google 雲端硬碟時,才要求存取權,這樣比較自然。
在這種情況下,應用程式可能會在登入時要求 openid 和 profile 範圍來執行基本登入,然後在第一次要求儲存混音時要求 https://www.googleapis.com/auth/drive.file 範圍。
透過漸進式授權取得的存取權杖,適用下列規則:
- 這個權杖可用於存取與新合併授權中任何範圍相對應的資源。
- 使用合併授權的更新權杖取得存取權杖時,存取權杖代表合併授權,可用於回應中包含的任何
scope值。 - 合併授權包含使用者授予 API 專案的所有範圍,即使授權是從不同用戶端要求也一樣。舉例來說,如果使用者透過應用程式的電腦用戶端授予一個範圍的存取權,然後透過行動用戶端授予另一個範圍的存取權,則合併授權會包含這兩個範圍。
- 如果您撤銷代表合併授權的權杖,系統會同時撤銷代表相關聯使用者存取該授權所有範圍的權限。
下列程式碼範例說明如何將範圍新增至現有的存取權杖。這樣一來,應用程式就不必管理多個存取權杖。
OAuth 2.0 端點
如要將範圍新增至現有的存取權杖,請在向 Google 的 OAuth 2.0 伺服器發出的要求中加入 include_granted_scopes 參數。
下列程式碼片段示範如何執行這項操作。這個程式碼片段假設您已將存取權杖有效的範圍,儲存在瀏覽器的本機儲存空間。(已完成樣本程式碼會在瀏覽器的本機儲存空間中設定 oauth2-test-params.scope 屬性,儲存存取權杖有效的範圍清單)。
這個程式碼片段會比較存取權杖有效的範圍,以及您要用於特定查詢的範圍。如果存取權杖不涵蓋該範圍,OAuth 2.0 流程就會啟動。這裡的 oauth2SignIn 函式與步驟 2 中提供的函式相同 (稍後在完整範例中也會提供)。
var SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly'; var params = JSON.parse(localStorage.getItem('oauth2-test-params')); var current_scope_granted = false; if (params.hasOwnProperty('scope')) { var scopes = params['scope'].split(' '); for (var s = 0; s < scopes.length; s++) { if (SCOPE == scopes[s]) { current_scope_granted = true; } } } if (!current_scope_granted) { oauth2SignIn(); // This function is defined elsewhere in this document. } else { // Since you already have access, you can proceed with the API request. }
撤銷權杖
在某些情況下,使用者可能想撤銷授予應用程式的存取權。使用者可以前往 帳戶設定撤銷存取權。詳情請參閱「可存取您帳戶的第三方網站和應用程式」支援文件的「移除網站或應用程式存取權」一節。
應用程式也可以透過程式輔助方式撤銷已獲得的存取權。如果使用者取消訂閱、移除應用程式,或應用程式所需的 API 資源大幅變更,則程式輔助撤銷功能就相當重要。換句話說,移除程序的一部分可能包括 API 要求,以確保先前授予應用程式的權限已移除。
OAuth 2.0 端點
如要透過程式輔助方式撤銷權杖,應用程式會向 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 和錯誤代碼。
下列 JavaScript 程式碼片段說明如何在不使用 Google API 用戶端程式庫 for JavaScript 的情況下,撤銷 JavaScript 中的權杖。由於 Google 用於撤銷權杖的 OAuth 2.0 端點不支援跨源資源共享 (CORS),因此程式碼會建立表單並將表單提交至端點,而不是使用 XMLHttpRequest() 方法發布要求。
function revokeAccess(accessToken) { // Google's OAuth 2.0 endpoint for revoking access tokens. var revokeTokenEndpoint = 'https://oauth2.googleapis.com/revoke'; // Create <form> element to use to POST data to the OAuth 2.0 endpoint. var form = document.createElement('form'); form.setAttribute('method', 'post'); form.setAttribute('action', revokeTokenEndpoint); // Add access token to the form so it is set as value of 'token' parameter. // This corresponds to the sample curl request, where the URL is: // https://oauth2.googleapis.com/revoke?token={token} var tokenField = document.createElement('input'); tokenField.setAttribute('type', 'hidden'); tokenField.setAttribute('name', 'token'); tokenField.setAttribute('value', accessToken); form.appendChild(tokenField); // Add form to page and submit it to actually revoke the token. document.body.appendChild(form); form.submit(); }
導入跨帳戶防護功能
為保護使用者帳戶,您應採取的額外步驟是使用 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
如要進一步瞭解如何實作跨帳戶防護,以及查看可用事件的完整清單,請參閱「 使用跨帳戶防護保護使用者帳戶 」頁面。