總覽
為了取得每個使用者存取權杖以呼叫 Google API,Google 提供多種 JavaScript 程式庫:
本指南提供從這些程式庫遷移至 Google Identity Services 程式庫的操作說明。
透過本指南,您將:
- 用 Identity Services 程式庫取代已淘汰的平台程式庫,並且
- 如果使用 API 用戶端程式庫,請移除已淘汰的
gapi.auth2
模組、其方法和物件,並替換成 Identity Services 對等項目。
如需 Identity Services JavaScript 程式庫變更內容的說明,請參閱總覽和使用者授權的運作方式,瞭解如何查看重要詞彙和概念。
如果您要尋找使用者註冊和登入的驗證作業,請參閱從 Google 登入遷移一文。
找出授權流程
有兩種可能的使用者授權流程:隱含和授權碼。
查看網頁應用程式,找出目前使用的授權流程類型。
表示網頁應用程式使用隱含流程:
- 網頁應用程式單純以瀏覽器為基礎,沒有任何後端平台。
- 使用者必須存在才能呼叫 Google API、應用程式只會使用存取權杖,且不需要更新權杖。
- 您的網頁應用程式會載入
apis.google.com/js/api.js
。 - 您的實作是以用戶端網頁應用程式適用的 OAuth 2.0 為基礎。
- 應用程式會使用 JavaScript 適用的 Google API 用戶端程式庫中的
gapi.client
或gapi.auth2
模組。
指出您的網頁應用程式使用授權碼流程:
您的導入結果取決於:
應用程式會在使用者的瀏覽器和後端平台上執行。
您的後端平台會託管授權碼端點。
您的後端平台會代表使用者呼叫 Google API,而且無需存在,也稱為離線模式。
更新權杖是由您的後端平台負責管理及儲存。
在某些情況下,程式碼集可能會同時支援這兩種流程。
選擇授權流程
開始遷移作業之前,您必須判斷要繼續執行現有流程,還是採用最符合自身需求的不同流程。
請參閱選擇授權流程一文,瞭解這兩種流程的主要差異和優缺點。
在大多數情況下,我們會建議您採用授權碼流程,因為此流程能為使用者帶來最高層級的安全性。實作這個流程也能讓您的平台更輕鬆地新增離線功能,例如擷取更新,向使用者通知使用者的日曆、相片和訂閱項目等重大異動。
使用下方的選取器選擇授權流程。
隱式流程
取得使用者在瀏覽器內使用時,取得的存取權杖。
隱式流程範例會顯示遷移至 Identity Services 前後的網頁應用程式。
授權碼流程
由 Google 核發的一個使用者授權碼會傳送到您的後端平台,然後在後端平台中交換存取權杖和更新權杖。
授權程式碼流程範例會顯示遷移至 Identity Services 前後的網頁應用程式。
在本指南中,按照粗體顯示的操作說明,為「Add」、「Remove」、「Update」或「Replace」現有功能操作。
瀏覽器內網頁應用程式的變更
本節會說明遷移至 Google Identity 服務 JavaScript 程式庫時,您會在瀏覽器內網頁應用程式上做出的變更。
找出受影響的程式碼和測試
偵錯 Cookie 可協助您找出受影響的程式碼,並測試淘汰後的行為。
在大型或複雜的應用程式中,可能很難找出受到 gapi.auth2
模組淘汰影響的所有程式碼。如要向主控台記錄即將淘汰功能的現有使用情況,請將 G_AUTH2_MIGRATION
Cookie 的值設為 informational
。您可以選擇新增冒號,後面加上一個鍵/值,以便一併記錄至工作階段儲存空間。登入並收到憑證審查後,或將收集的記錄檔傳送至後端進行後續分析。舉例來說,informational:showauth2use
會將來源和網址儲存到名為 showauth2use
的工作階段儲存金鑰。
如要在停止載入 gapi.auth2
模組時驗證應用程式行為,請將 G_AUTH2_MIGRATION
Cookie 的值設為 enforced
。這樣即可在強制執行日期之前測試淘汰後的行為。
可能的 G_AUTH2_MIGRATION
Cookie 值:
enforced
不要載入gapi.auth2
模組。informational
在 JS 控制台中記錄已淘汰功能的記錄。設定選用的索引鍵名稱時,一併記錄至工作階段儲存空間:informational:key-name
。
為了盡可能降低對使用者的影響,建議您先在開發和測試期間在本機設定這個 Cookie,再於實際工作環境中使用。
程式庫與模組
gapi.auth2
模組會管理登入的使用者驗證和授權隱含流程,請將這個已淘汰的模組及其物件和方法替換為 Google Identity 服務程式庫。
在文件中將 Identity Services 程式庫新增至網頁應用程式:
<script src="https://accounts.google.com/gsi/client" async defer></script>
移除使用 gapi.load('auth2',
function)
載入 auth2
模組的所有執行個體。
Google Identity Services 程式庫會取代 gapi.auth2
模組的使用方式。您可以放心繼續使用 JavaScript 適用的 Google API 用戶端程式庫中的 gapi.client
模組,並善用這個模組從探索文件自動建立可呼叫的 JS 方法、批次處理多個 API 呼叫,以及 CORS 管理功能。
餅乾
使用者授權不需要使用 Cookie。
如要進一步瞭解使用者驗證如何使用 Cookie,請參閱「從 Google 登入遷移」一文,以及「Google 如何使用 Cookie」以及其他 Google 產品和服務使用的 Cookie。
憑證
Google Identity 服務會將使用者驗證和授權拆分為兩項不同的作業,使用者憑證則各自獨立:用於識別使用者的 ID 憑證會和用於授權的存取權杖分開傳回。
如要查看這些變更,請參閱範例憑證。
隱式流程
從授權流程中移除要處理的使用者個人資料,藉此分隔使用者驗證與授權。
移除下列 Google 登入 JavaScript 用戶端參考資料:
方法
GoogleUser.getBasicProfile()
GoogleUser.getId()
授權碼流程
Identity Services 會將瀏覽器內憑證分割為 ID 權杖和存取權杖。這項異動不適用於透過您的後端平台直接呼叫 Google OAuth 2.0 端點,或是經由平台安全伺服器上執行的程式庫 (例如 Google API Node.js 用戶端) 取得的憑證。
工作階段狀態
過去,Google 登入可讓您透過下列功能管理使用者登入狀態:
- 監控使用者工作階段狀態的回呼處理常式。
- 事件監聽器:用於事件和使用者 Google 帳戶的登入狀態變更。
您必須負責管理網頁應用程式的登入狀態和使用者工作階段。
移除下列 Google 登入 JavaScript 用戶端參考資料:
物件:
gapi.auth2.SignInOptions
方法:
GoogleAuth.attachClickHandler()
GoogleAuth.isSignedIn()
GoogleAuth.isSignedIn.get()
GoogleAuth.isSignedIn.listen()
GoogleAuth.signIn()
GoogleAuth.signOut()
GoogleAuth.currentUser.get()
GoogleAuth.currentUser.listen()
GoogleUser.isSignedIn()
用戶端設定
更新網頁應用程式,以初始化權杖用戶端,以進行隱含或授權代碼的流程。
移除下列 Google 登入 JavaScript 用戶端參考資料:
物件:
gapi.auth2.ClientConfig
gapi.auth2.OfflineAccessOptions
方法:
gapi.auth2.getAuthInstance()
GoogleUser.grant()
隱式流程
按照「初始化權杖用戶端」中的範例,新增 TokenClientConfig
物件和 initTokenClient()
呼叫以設定網頁應用程式。
將 Google 登入 JavaScript 用戶端參考資料替換為 Google Identity Services:
物件:
gapi.auth2.AuthorizeConfig
與TokenClientConfig
合作
方法:
gapi.auth2.init()
與google.accounts.oauth2.initTokenClient()
合作
參數:
gapi.auth2.AuthorizeConfig.login_hint
搭配TokenClientConfig.login_hint
。gapi.auth2.GoogleUser.getHostedDomain()
(TokenClientConfig.hd
)。
授權碼流程
新增 CodeClientConfig
物件和 initCodeClient()
呼叫以設定網頁應用程式,請按照「初始化程式碼用戶端」中的範例操作。
從隱含式切換為授權碼流程時:
移除 Google 登入 JavaScript 用戶端參考資料
物件:
gapi.auth2.AuthorizeConfig
方法:
gapi.auth2.init()
參數:
gapi.auth2.AuthorizeConfig.login_hint
gapi.auth2.GoogleUser.getHostedDomain()
權杖要求
使用者手勢 (例如按鈕點擊) 會產生一個要求,該要求會透過隱含流程直接傳回使用者的瀏覽器,或先交換每個使用者的存取碼來更新存取權杖並更新權杖。
隱式流程
當使用者登入並與 Google 處於有效工作階段時,即可在瀏覽器內取得及使用存取權杖。就隱式模式而言,即使先前已提出要求,使用者手勢也必須透過使用者手勢要求存取權杖。
取代 Google 登入 JavaScript 用戶端參考資料:替換為 Google Identity Services:
方法:
gapi.auth2.authorize()
與TokenClient.requestAccessToken()
合作GoogleUser.reloadAuthResponse()
搭配TokenClient.requestAccessToken()
新增連結或按鈕以呼叫 requestAccessToken()
,啟動彈出式使用者體驗流程來要求存取權杖,或是在現有權杖過期時取得新權杖。
更新程式碼集為:
- 使用
requestAccessToken()
觸發 OAuth 2.0 權杖流程。 - 使用
requestAccessToken
和OverridableTokenClientConfig
將多個範圍的單一要求分成多個較小的要求,以便支援漸進式授權。 - 在現有權杖過期或遭撤銷時,要求新的權杖。
使用多個範圍時,您可能需要對程式碼集進行結構性變更,以只要求存取範圍 (而不是一次要求所有),這稱為「漸進式授權」。每個要求應包含的範圍越少,最好是單一範圍。如要進一步瞭解如何更新應用程式以便進行漸進式授權,請參閱「處理使用者同意聲明」一文。
存取權杖過期時,gapi.auth2
模組會自動為網頁應用程式取得新的有效存取權杖。為了提升使用者安全性,Google Identity Services 程式庫不支援這項自動權杖更新程序。您必須更新網頁應用程式,才能偵測過期的存取權杖,並要求新的權杖。詳情請參閱下方的「權杖處理」一節。
授權碼流程
新增連結或按鈕,呼叫 requestCode()
向 Google 要求授權碼。如需範例,請參閱觸發 OAuth 2.0 程式碼流程。
如要進一步瞭解如何回應過期或撤銷的存取權杖,請參閱下方的「權杖處理」一節。
權杖處理
新增錯誤處理機制,可在使用過期或撤銷的存取權杖時,偵測失敗的 Google API 呼叫,並要求新的有效存取權杖。
如果使用過期或撤銷的存取權杖,Google API 會傳回 HTTP 狀態碼 401 Unauthorized
和 invalid_token
錯誤訊息。範例請見無效的憑證回應。
權杖已過期
存取權杖是短期的,通常只有幾分鐘才有效。
撤銷權杖
Google 帳戶擁有者可隨時撤銷先前授予的權限。這樣做會導致現有的存取權杖和更新權杖失效。您的平台可以使用 revoke()
或 Google 帳戶觸發撤銷程序。
取代 Google 登入 JavaScript 用戶端參考資料:替換為 Google Identity Services:
方法:
getAuthInstance().disconnect()
與google.accounts.oauth2.revoke()
合作GoogleUser.disconnect()
與google.accounts.oauth2.revoke()
合作
使用者在您的平台上刪除自己的帳戶時,請呼叫 revoke
,或移除與應用程式分享資料而移除的同意聲明。
使用者同意聲明提示
每當您的網頁應用程式或後端平台要求存取權杖時,Google 就會向使用者顯示同意聲明對話方塊。請參閱 Google 向使用者顯示的同意聲明對話方塊範例。
在向應用程式核發存取權杖之前,您必須先有現有的有效 Google 工作階段,才能要求使用者同意並記錄結果。如果尚未建立 Google 帳戶,則可能需要登入使用者登入 Google 帳戶。
使用者登入
使用者可能是在另一個瀏覽器分頁中登入 Google 帳戶,或透過瀏覽器或作業系統原生登入 Google 帳戶。建議您在網站中加入「使用 Google 帳戶登入」功能,以便在使用者首次開啟應用程式時,在 Google 帳戶和瀏覽器之間建立啟用的工作階段。這麼做的好處如下:
- 可盡量減少使用者必須登入的次數。如果要求存取權杖沒有啟用,系統就會啟動 Google 帳戶登入程序。
- 直接使用 JWT ID 權杖 credential
email
欄位,做為CodeClientConfig
或TokenClientConfig
物件中的login_hint
參數值。如果您的平台不會維護使用者帳戶管理系統,這項功能就特別實用。 - 查詢 Google 帳戶並連結至平台上現有的本機使用者帳戶,盡量減少平台上的重複帳戶。
- 建立新的本機帳戶時,註冊對話方塊和流程可明確與使用者驗證對話方塊和流程分開,藉此減少必要步驟數並提高流失率。
登入後,在核發存取權杖前,使用者必須針對要求的範圍提供應用程式同意聲明。
權杖和同意聲明回應
同意後,系統會傳回存取權杖,以及使用者核准或拒絕的範圍清單。
精細權限可讓使用者核准或拒絕個別範圍。要求存取多個範圍時,每個範圍會不受其他範圍的影響而受到授予或拒絕。根據使用者選擇,應用程式會選擇性啟用依附個別範圍的功能。
隱式流程
將 Google 登入 JavaScript 用戶端參考資料替換為 Google Identity Services:
物件:
gapi.auth2.AuthorizeResponse
與TokenClient.TokenResponse
合作gapi.auth2.AuthResponse
與TokenClient.TokenResponse
合作
方法:
GoogleUser.hasGrantedScopes()
搭配google.accounts.oauth2.hasGrantedAllScopes()
GoogleUser.getGrantedScopes()
搭配google.accounts.oauth2.hasGrantedAllScopes()
移除 Google 登入 JavaScript 用戶端參考資料:
方法:
GoogleUser.getAuthResponse()
按照這個精細權限範例,使用 hasGrantedAllScopes()
和 hasGrantedAnyScope()
更新網頁應用程式。
授權碼流程
按照授權碼處理中的操作說明,更新或新增授權碼端點至您的後端平台。
更新平台,按照「使用程式碼模型」指南中所述的步驟驗證要求,並取得存取權杖並重新整理權杖。
更新平台,根據使用者核准的個別範圍選擇性地啟用或停用功能與功能,詳細操作步驟請參閱漸進式授權和檢查使用者授予的存取權範圍。
隱式流程範例
舊式
GAPI 用戶端程式庫
適用於 JavaScript 的 Google API 用戶端程式庫範例,會透過彈出式對話方塊向使用者顯示同意聲明。
gapi.client.init()
會自動載入並使用 gapi.auth2
模組,因此會隱藏起來。
<!DOCTYPE html>
<html>
<head>
<script src="https://apis.google.com/js/api.js"></script>
<script>
function start() {
gapi.client.init({
'apiKey': 'YOUR_API_KEY',
'clientId': 'YOUR_CLIENT_ID',
'scope': 'https://www.googleapis.com/auth/cloud-translation',
'discoveryDocs': ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
}).then(function() {
// Execute an API request which is returned as a Promise.
// The method name language.translations.list comes from the API discovery.
return gapi.client.language.translations.list({
q: 'hello world',
source: 'en',
target: 'de',
});
}).then(function(response) {
console.log(response.result.data.translations[0].translatedText);
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
};
// Load the JavaScript client library and invoke start afterwards.
gapi.load('client', start);
</script>
</head>
<body>
<div id="results"></div>
</body>
</html>
JS 用戶端程式庫
在瀏覽器中執行的用戶端網頁應用程式 OAuth 2.0 會使用彈出式對話方塊徵求使用者同意。
已手動載入 gapi.auth2
模組。
<!DOCTYPE html>
<html><head></head><body>
<script>
var GoogleAuth;
var SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly';
function handleClientLoad() {
// Load the API's client and auth2 modules.
// Call the initClient function after the modules load.
gapi.load('client:auth2', initClient);
}
function initClient() {
// In practice, your app can retrieve one or more discovery documents.
var discoveryUrl = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';
// Initialize the gapi.client object, which app uses to make API requests.
// Get API key and client ID from API Console.
// 'scope' field specifies space-delimited list of access scopes.
gapi.client.init({
'apiKey': 'YOUR_API_KEY',
'clientId': 'YOUR_CLIENT_ID',
'discoveryDocs': [discoveryUrl],
'scope': SCOPE
}).then(function () {
GoogleAuth = gapi.auth2.getAuthInstance();
// Listen for sign-in state changes.
GoogleAuth.isSignedIn.listen(updateSigninStatus);
// Handle initial sign-in state. (Determine if user is already signed in.)
var user = GoogleAuth.currentUser.get();
setSigninStatus();
// Call handleAuthClick function when user clicks on
// "Sign In/Authorize" button.
$('#sign-in-or-out-button').click(function() {
handleAuthClick();
});
$('#revoke-access-button').click(function() {
revokeAccess();
});
});
}
function handleAuthClick() {
if (GoogleAuth.isSignedIn.get()) {
// User is authorized and has clicked "Sign out" button.
GoogleAuth.signOut();
} else {
// User is not signed in. Start Google auth flow.
GoogleAuth.signIn();
}
}
function revokeAccess() {
GoogleAuth.disconnect();
}
function setSigninStatus() {
var user = GoogleAuth.currentUser.get();
var isAuthorized = user.hasGrantedScopes(SCOPE);
if (isAuthorized) {
$('#sign-in-or-out-button').html('Sign out');
$('#revoke-access-button').css('display', 'inline-block');
$('#auth-status').html('You are currently signed in and have granted ' +
'access to this app.');
} else {
$('#sign-in-or-out-button').html('Sign In/Authorize');
$('#revoke-access-button').css('display', 'none');
$('#auth-status').html('You have not authorized this app or you are ' +
'signed out.');
}
}
function updateSigninStatus() {
setSigninStatus();
}
</script>
<button id="sign-in-or-out-button"
style="margin-left: 25px">Sign In/Authorize</button>
<button id="revoke-access-button"
style="display: none; margin-left: 25px">Revoke access</button>
<div id="auth-status" style="display: inline; padding-left: 25px"></div><hr>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
</body></html>
OAuth 2.0 端點
在瀏覽器中執行的用戶端網頁應用程式 OAuth 2.0 會重新導向至 Google,以便取得使用者同意。
這個範例顯示直接從使用者的瀏覽器呼叫 Google OAuth 2.0 端點,但不使用 gapi.auth2
模組或 JavaScript 程式庫。
<!DOCTYPE html>
<html><head></head><body>
<script>
var YOUR_CLIENT_ID = 'REPLACE_THIS_VALUE';
var YOUR_REDIRECT_URI = 'REPLACE_THIS_VALUE';
var fragmentString = location.hash.substring(1);
// Parse query string to see if page request is coming from OAuth 2.0 server.
var params = {};
var regex = /([^&=]+)=([^&]*)/g, m;
while (m = regex.exec(fragmentString)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
if (Object.keys(params).length > 0) {
localStorage.setItem('oauth2-test-params', JSON.stringify(params) );
if (params['state'] && params['state'] == 'try_sample_request') {
trySampleRequest();
}
}
// 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']) {
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 {
oauth2SignIn();
}
}
/*
* Create form to request access token from Google's OAuth 2.0 server.
*/
function oauth2SignIn() {
// 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',
'state': 'try_sample_request',
'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>
新方式
僅限 GIS
此範例只會顯示使用權杖模型的 Google Identity Service JavaScript 程式庫,以及使用者同意聲明的彈出式對話方塊。我們會提供說明,讓使用者只需執行最低步驟,即可設定用戶端、要求和取得存取權杖,以及呼叫 Google API。
<!DOCTYPE html>
<html>
<head>
<script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
</head>
<body>
<script>
var client;
var access_token;
function initClient() {
client = google.accounts.oauth2.initTokenClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly \
https://www.googleapis.com/auth/contacts.readonly',
callback: (tokenResponse) => {
access_token = tokenResponse.access_token;
},
});
}
function getToken() {
client.requestAccessToken();
}
function revokeToken() {
google.accounts.oauth2.revoke(access_token, () => {console.log('access token revoked')});
}
function loadCalendar() {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.googleapis.com/calendar/v3/calendars/primary/events');
xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
xhr.send();
}
</script>
<h1>Google Identity Services Authorization Token model</h1>
<button onclick="getToken();">Get access token</button><br><br>
<button onclick="loadCalendar();">Load Calendar</button><br><br>
<button onclick="revokeToken();">Revoke token</button>
</body>
</html>
GAPI 非同步/等待
這個範例說明如何使用權杖模型新增 Google Identity Service 程式庫、移除 gapi.auth2
模組,以及使用 JavaScript 適用的 Google API 用戶端程式庫呼叫 API。
Promis、非同步和 await 可用於強制執行程式庫載入訂單,以及擷取及重試授權錯誤。您必須先取得有效的存取權杖,才能發出 API 呼叫。
如果存取權杖遺失,使用者應按下「顯示日曆」按鈕;在初次載入網頁時,或是在存取權杖過期之後,按下「顯示日曆」按鈕。
<!DOCTYPE html>
<html>
<head></head>
<body>
<h1>GAPI with GIS async/await</h1>
<button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br>
<button id="revokeBtn" onclick="revokeToken();">Revoke access token</button>
<script>
const gapiLoadPromise = new Promise((resolve, reject) => {
gapiLoadOkay = resolve;
gapiLoadFail = reject;
});
const gisLoadPromise = new Promise((resolve, reject) => {
gisLoadOkay = resolve;
gisLoadFail = reject;
});
var tokenClient;
(async () => {
document.getElementById("showEventsBtn").style.visibility="hidden";
document.getElementById("revokeBtn").style.visibility="hidden";
// First, load and initialize the gapi.client
await gapiLoadPromise;
await new Promise((resolve, reject) => {
// NOTE: the 'auth2' module is no longer loaded.
gapi.load('client', {callback: resolve, onerror: reject});
});
await gapi.client.init({
// NOTE: OAuth2 'scope' and 'client_id' parameters have moved to initTokenClient().
})
.then(function() { // Load the Calendar API discovery document.
gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
});
// Now load the GIS client
await gisLoadPromise;
await new Promise((resolve, reject) => {
try {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
prompt: 'consent',
callback: '', // defined at request time in await/promise scope.
});
resolve();
} catch (err) {
reject(err);
}
});
document.getElementById("showEventsBtn").style.visibility="visible";
document.getElementById("revokeBtn").style.visibility="visible";
})();
async function getToken(err) {
if (err.result.error.code == 401 || (err.result.error.code == 403) &&
(err.result.error.status == "PERMISSION_DENIED")) {
// The access token is missing, invalid, or expired, prompt for user consent to obtain one.
await new Promise((resolve, reject) => {
try {
// Settle this promise in the response callback for requestAccessToken()
tokenClient.callback = (resp) => {
if (resp.error !== undefined) {
reject(resp);
}
// GIS has automatically updated gapi.client with the newly issued access token.
console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
resolve(resp);
};
tokenClient.requestAccessToken();
} catch (err) {
console.log(err)
}
});
} else {
// Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
throw new Error(err);
}
}
function showEvents() {
// Try to fetch a list of Calendar events. If a valid access token is needed,
// prompt to obtain one and then retry the original request.
gapi.client.calendar.events.list({ 'calendarId': 'primary' })
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => getToken(err)) // for authorization errors obtain an access token
.then(retry => gapi.client.calendar.events.list({ 'calendarId': 'primary' }))
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => console.log(err)); // cancelled by user, timeout, etc.
}
function revokeToken() {
let cred = gapi.client.getToken();
if (cred !== null) {
google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)});
gapi.client.setToken('');
}
}
</script>
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoadOkay()" onerror="gapiLoadFail(event)"></script>
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoadOkay()" onerror="gisLoadFail(event)"></script>
</body>
</html>
GAPI 回呼
這個範例說明如何使用權杖模型新增 Google Identity Service 程式庫、移除 gapi.auth2
模組,以及使用 JavaScript 適用的 Google API 用戶端程式庫呼叫 API。
變數是用來強制執行程式庫載入順序。傳回有效的存取權杖後,系統就會從回呼內發出 GAPI 呼叫。
使用者必須在網頁首次載入和要重新整理日曆資訊時再次按下「顯示日曆」按鈕。
<!DOCTYPE html>
<html>
<head>
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoad()"></script>
<script async defer src="https://accounts.google.com/gsi/client" onload="gisInit()"></script>
</head>
<body>
<h1>GAPI with GIS callbacks</h1>
<button id="showEventsBtn" onclick="showEvents();">Show Calendar</button><br><br>
<button id="revokeBtn" onclick="revokeToken();">Revoke access token</button>
<script>
let tokenClient;
let gapiInited;
let gisInited;
document.getElementById("showEventsBtn").style.visibility="hidden";
document.getElementById("revokeBtn").style.visibility="hidden";
function checkBeforeStart() {
if (gapiInited && gisInited){
// Start only when both gapi and gis are initialized.
document.getElementById("showEventsBtn").style.visibility="visible";
document.getElementById("revokeBtn").style.visibility="visible";
}
}
function gapiInit() {
gapi.client.init({
// NOTE: OAuth2 'scope' and 'client_id' parameters have moved to initTokenClient().
})
.then(function() { // Load the Calendar API discovery document.
gapi.client.load('https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest');
gapiInited = true;
checkBeforeStart();
});
}
function gapiLoad() {
gapi.load('client', gapiInit)
}
function gisInit() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
callback: '', // defined at request time
});
gisInited = true;
checkBeforeStart();
}
function showEvents() {
tokenClient.callback = (resp) => {
if (resp.error !== undefined) {
throw(resp);
}
// GIS has automatically updated gapi.client with the newly issued access token.
console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
gapi.client.calendar.events.list({ 'calendarId': 'primary' })
.then(calendarAPIResponse => console.log(JSON.stringify(calendarAPIResponse)))
.catch(err => console.log(err));
document.getElementById("showEventsBtn").innerText = "Refresh Calendar";
}
// Conditionally ask users to select the Google Account they'd like to use,
// and explicitly obtain their consent to fetch their Calendar.
// NOTE: To request an access token a user gesture is necessary.
if (gapi.client.getToken() === null) {
// Prompt the user to select a Google Account and asked for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({prompt: 'consent'});
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({prompt: ''});
}
}
function revokeToken() {
let cred = gapi.client.getToken();
if (cred !== null) {
google.accounts.oauth2.revoke(cred.access_token, () => {console.log('Revoked: ' + cred.access_token)});
gapi.client.setToken('');
document.getElementById("showEventsBtn").innerText = "Show Calendar";
}
}
</script>
</body>
</html>
授權代碼流程範例
Google Identity Service 程式庫的彈出式視窗使用者體驗都可以使用網址重新導向,將授權碼直接傳回後端權杖端點,或是是在使用者瀏覽器中執行的 JavaScript 回呼處理常式,透過 Proxy 傳送回應至您的平台。不論是哪一種情況,後端平台都會完成 OAuth 2.0 流程,以取得有效的更新和存取權杖。
舊式
伺服器端網頁應用程式
伺服器端應用程式的 Google 登入功能:使用重新導向至 Google 的方式取得使用者同意。
<!DOCTYPE html>
<html>
<head>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="https://apis.google.com/js/client:platform.js?onload=start" async defer></script>
<script>
function start() {
gapi.load('auth2', function() {
auth2 = gapi.auth2.init({
client_id: 'YOUR_CLIENT_ID',
api_key: 'YOUR_API_KEY',
discovery_docs: ['https://www.googleapis.com/discovery/v1/apis/translate/v2/rest'],
// Scopes to request in addition to 'profile' and 'email'
scope: 'https://www.googleapis.com/auth/cloud-translation',
});
});
}
function signInCallback(authResult) {
if (authResult['code']) {
console.log("sending AJAX request");
// Send authorization code obtained from Google to backend platform
$.ajax({
type: 'POST',
url: 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URL',
// Always include an X-Requested-With header to protect against CSRF attacks.
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
contentType: 'application/octet-stream; charset=utf-8',
success: function(result) {
console.log(result);
},
processData: false,
data: authResult['code']
});
} else {
console.log('error: failed to obtain authorization code')
}
}
</script>
</head>
<body>
<button id="signinButton">Sign In With Google</button>
<script>
$('#signinButton').click(function() {
// Obtain an authorization code from Google
auth2.grantOfflineAccess().then(signInCallback);
});
</script>
</body>
</html>
使用重新導向的 HTTP/REST
使用 OAuth 2.0 處理網頁應用程式應用程式,從使用者的瀏覽器傳送授權碼至後端平台。將使用者瀏覽器重新導向至 Google 來處理使用者同意。
/\*
\* 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_AUTHORIZATION_CODE_ENDPOINT_URL',
'response\_type': 'token',
'scope': 'https://www.googleapis.com/auth/drive.metadata.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();
}
新方式
GIS 彈出式視窗使用者體驗
這個範例只顯示使用授權碼模型的 Google Identity Service JavaScript 程式庫,用於提供使用者同意,以及回呼處理常式接收 Google 提供的授權碼。我們會提供基本步驟,說明如何設定用戶端、取得同意聲明並將授權碼傳送至後端平台。
<!DOCTYPE html>
<html>
<head>
<script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
</head>
<body>
<script>
var client;
function initClient() {
client = google.accounts.oauth2.initCodeClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
ux_mode: 'popup',
callback: (response) => {
var code_receiver_uri = 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URI',
// Send auth code to your backend platform
const xhr = new XMLHttpRequest();
xhr.open('POST', code_receiver_uri, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onload = function() {
console.log('Signed in as: ' + xhr.responseText);
};
xhr.send('code=' + response.code);
// After receipt, the code is exchanged for an access token and
// refresh token, and the platform then updates this web app
// running in user's browser with the requested calendar info.
},
});
}
function getAuthCode() {
// Request authorization code and obtain user consent
client.requestCode();
}
</script>
<button onclick="getAuthCode();">Load Your Calendar</button>
</body>
</html>
GIS 重新導向使用者體驗
授權碼模型支援彈出式視窗和重新導向使用者體驗模式,可將每位使用者的授權碼傳送至您的平台代管的端點。重新導向的使用者體驗模式如下所示:
<!DOCTYPE html>
<html>
<head>
<script src="https://accounts.google.com/gsi/client" onload="initClient()" async defer></script>
</head>
<body>
<script>
var client;
function initClient() {
client = google.accounts.oauth2.initCodeClient({
client_id: 'YOUR_CLIENT_ID',
scope: 'https://www.googleapis.com/auth/calendar.readonly \
https://www.googleapis.com/auth/photoslibrary.readonly',
ux_mode: 'redirect',
redirect_uri: 'YOUR_AUTHORIZATION_CODE_ENDPOINT_URI'
});
}
// Request an access token
function getAuthCode() {
// Request authorization code and obtain user consent
client.requestCode();
}
</script>
<button onclick="getAuthCode();">Load Your Calendar</button>
</body>
</html>
JavaScript 程式庫
Google Identity 服務是單一 JavaScript 程式庫,用於使用者驗證和授權,可整合及取代多個不同程式庫和模組中的功能與功能:
遷移至 Identity Services 時請採取下列行動:
現有的 JS 程式庫 | 新增 JS 程式庫 | 附註 |
---|---|---|
apis.google.com/js/api.js |
accounts.google.com/gsi/client |
新增程式庫並按照隱含流程操作。 |
apis.google.com/js/client.js |
accounts.google.com/gsi/client |
新增程式庫和授權碼流程。 |
程式庫快速參考資源
「舊版」Google 登入 JavaScript 用戶端程式庫資料庫與新「Google Identity 服務」程式庫與附註的物件和方法比較,提供遷移期間需要取得的額外資訊和操作選項。
舊優惠 | 新功能 | 附註 |
---|---|---|
GoogleAuth 物件和關聯方法: | ||
GoogleAuth.attachClickHandler() | 移除 | |
GoogleAuth.currentUser.get() | 移除 | |
GoogleAuth.currentUser.listen() | 移除 | |
GoogleAuth.disconnect() | google.accounts.OAuth2.revoke | 請更換為新版。https://myaccount.google.com/permissions 也可能執行撤銷操作 |
GoogleAuth.grantOfflineAccess() | 移除,請按照授權碼流程操作。 | |
GoogleAuth.isSignedIn.get() | 移除 | |
GoogleAuth.isSignedIn.listen() | 移除 | |
GoogleAuth.signIn() | 移除 | |
GoogleAuth.signOut() | 移除 | |
GoogleAuth.then() | 移除 | |
GoogleUser 物件和相關方法: | ||
GoogleUser.disconnect() | google.accounts.id.revoke | 請更換為新版。https://myaccount.google.com/permissions 也可能執行撤銷操作 |
GoogleUser.getAuthResponse() | requestCode() 或 requestAccessToken() | 更換為新舊 |
GoogleUser.getBasicProfile() | ,即可逐一移除離線觀看清單內的影片;請改用 ID 權杖,請參閱從 Google 登入遷移一文。 | |
GoogleUser.getGrantedScopes() | hasGrantedAnyScope() | 更換為新舊 |
GoogleUser.getHostDomain() | 移除 | |
GoogleUser.getId() | 移除 | |
GoogleUser.grantOfflineAccess() | 移除,請按照授權碼流程操作。 | |
GoogleUser.grant() | 移除 | |
GoogleUser.hasGrantedScopes() | hasGrantedAnyScope() | 更換為新舊 |
GoogleUser.isSignedIn() | 移除 | |
GoogleUser.reloadAuthResponse() | requestAccessToken() | 請移除舊值,呼叫新的憑證來取代已過期或撤銷的存取權杖。 |
gapi.auth2 物件及相關方法: | ||
gapi.auth2.AuthorizeConfig 物件 | TokenClientConfig 或 CodeClientConfig | 更換為新舊 |
gapi.auth2.AuthorizeResponse 物件 | 移除 | |
gapi.auth2.AuthResponse 物件 | 移除 | |
gapi.auth2.authorized() | requestCode() 或 requestAccessToken() | 更換為新舊 |
gapi.auth2.ClientConfig() | TokenClientConfig 或 CodeClientConfig | 更換為新舊 |
gapi.auth2.getAuthInstance() | 移除 | |
gapi.auth2.init() | initTokenClient() 或 initCodeClient() | 更換為新舊 |
gapi.auth2.OfflineAccessOptions 物件 | 移除 | |
gapi.auth2.SignInOptions 物件 | 移除 | |
gapi.signin2 物件與相關方法: | ||
gapi.signin2.render() | ,即可逐一移除離線觀看清單內的影片;載入 g_id_signin 元素或對 google.accounts.id.renderButton 的 JS 呼叫,會觸發使用者登入 Google 帳戶的程序。 |
範例憑證
現有憑證
Google 登入平台程式庫、適用於 JavaScript 的 Google API 用戶端程式庫,或對 Google Auth 2.0 端點的直接呼叫會在單一回應中傳回 OAuth 2.0 存取權杖和 OpenID Connect ID 權杖。
同時包含 access_token
和 id_token
的回應範例:
{
"token_type": "Bearer",
"access_token": "ya29.A0ARrdaM-SmArZaCIh68qXsZSzyeU-8mxhQERHrP2EXtxpUuZ-3oW8IW7a6D2J6lRnZrRj8S6-ZcIl5XVEqnqxq5fuMeDDH_6MZgQ5dgP7moY-yTiKR5kdPm-LkuPM-mOtUsylWPd1wpRmvw_AGOZ1UUCa6UD5Hg",
"scope": "https://www.googleapis.com/auth/calendar.readonly",
"login_hint": "AJDLj6I2d1RH77cgpe__DdEree1zxHjZJr4Q7yOisoumTZUmo5W2ZmVFHyAomUYzLkrluG-hqt4RnNxrPhArd5y6p8kzO0t8xIfMAe6yhztt6v2E-_Bb4Ec3GLFKikHSXNh5bI-gPrsI",
"expires_in": 3599,
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjkzNDFhYmM0MDkyYjZmYzAzOGU0MDNjOTEwMjJkZDNlNDQ1MzliNTYiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiYXpwIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXVkIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwic3ViIjoiMTE3NzI2NDMxNjUxOTQzNjk4NjAwIiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJkYWJyaWFuQGdvb2dsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXRfaGFzaCI6IkJBSW55TjN2MS1ZejNLQnJUMVo0ckEiLCJuYW1lIjoiQnJpYW4gRGF1Z2hlcnR5IiwicGljdHVyZSI6Imh0dHBzOi8vbGgzLmdvb2dsZXVzZXJjb250ZW50LmNvbS9hLS9BT2gxNEdnenAyTXNGRGZvbVdMX3VDemRYUWNzeVM3ZGtxTE5ybk90S0QzVXNRPXM5Ni1jIiwiZ2l2ZW5fbmFtZSI6IkJyaWFuIiwiZmFtaWx5X25hbWUiOiJEYXVnaGVydHkiLCJsb2NhbGUiOiJlbiIsImlhdCI6MTYzODk5MTYzOCwiZXhwIjoxNjM4OTk1MjM4LCJqdGkiOiI5YmRkZjE1YWFiNzE2ZDhjYmJmNDYwMmM1YWM3YzViN2VhMDQ5OTA5In0.K3EA-3Adw5HA7O8nJVCsX1HmGWxWzYk3P7ViVBb4H4BoT2-HIgxKlx1mi6jSxIUJGEekjw9MC-nL1B9Asgv1vXTMgoGaNna0UoEHYitySI23E5jaMkExkTSLtxI-ih2tJrA2ggfA9Ekj-JFiMc6MuJnwcfBTlsYWRcZOYVw3QpdTZ_VYfhUu-yERAElZCjaAyEXLtVQegRe-ymScra3r9S92TA33ylMb3WDTlfmDpWL0CDdDzby2asXYpl6GQ7SdSj64s49Yw6mdGELZn5WoJqG7Zr2KwIGXJuSxEo-wGbzxNK-mKAiABcFpYP4KHPEUgYyz3n9Vqn2Tfrgp-g65BQ",
"session_state": {
"extraQueryParams": {
"authuser": "0"
}
},
"first_issued_at": 1638991637982,
"expires_at": 1638995236982,
"idpId": "google"
}
Google Identity 服務憑證
Google Identity Services 程式庫會傳回:
是授權時使用的存取權杖:
{ "access_token": "ya29.A0ARrdaM_LWSO-uckLj7IJVNSfnUityT0Xj-UCCrGxFQdxmLiWuAosnAKMVQ2Z0LLqeZdeJii3TgULp6hR_PJxnInBOl8UoUwWoqsrGQ7-swxgy97E8_hnzfhrOWyQBmH6zs0_sUCzwzhEr_FAVqf92sZZHphr0g", "token_type": "Bearer", "expires_in": 3599, "scope": "https://www.googleapis.com/auth/calendar.readonly" }
或是用於驗證的 ID 權杖:
{ "clientId": "538344653255-758c5h5isc45vgk27d8h8deabovpg6to.apps.googleusercontent.com", "credential": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImMxODkyZWI0OWQ3ZWY5YWRmOGIyZTE0YzA1Y2EwZDAzMjcxNGEyMzciLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLCJuYmYiOjE2MzkxNTcyNjQsImF1ZCI6IjUzODM0NDY1MzI1NS03NThjNWg1aXNjNDV2Z2syN2Q4aDhkZWFib3ZwZzZ0by5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsInN1YiI6IjExNzcyNjQzMTY1MTk0MzY5ODYwMCIsIm5vbmNlIjoiZm9vYmFyIiwiaGQiOiJnb29nbGUuY29tIiwiZW1haWwiOiJkYWJyaWFuQGdvb2dsZS5jb20iLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiNTM4MzQ0NjUzMjU1LTc1OGM1aDVpc2M0NXZnazI3ZDhoOGRlYWJvdnBnNnRvLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwibmFtZSI6IkJyaWFuIERhdWdoZXJ0eSIsInBpY3R1cmUiOiJodHRwczovL2xoMy5nb29nbGV1c2VyY29udGVudC5jb20vYS0vQU9oMTRHZ3pwMk1zRkRmb21XTF91Q3pkWFFjc3lTN2RrcUxOcm5PdEtEM1VzUT1zOTYtYyIsImdpdmVuX25hbWUiOiJCcmlhbiIsImZhbWlseV9uYW1lIjoiRGF1Z2hlcnR5IiwiaWF0IjoxNjM5MTU3NTY0LCJleHAiOjE2MzkxNjExNjQsImp0aSI6IjRiOTVkYjAyZjU4NDczMmUxZGJkOTY2NWJiMWYzY2VhYzgyMmI0NjUifQ.Cr-AgMsLFeLurnqyGpw0hSomjOCU4S3cU669Hyi4VsbqnAV11zc_z73o6ahe9Nqc26kPVCNRGSqYrDZPfRyTnV6g1PIgc4Zvl-JBuy6O9HhClAK1HhMwh1FpgeYwXqrng1tifmuotuLQnZAiQJM73Gl-J_6s86Buo_1AIx5YAKCucYDUYYdXBIHLxrbALsA5W6pZCqqkMbqpTWteix-G5Q5T8LNsfqIu_uMBUGceqZWFJALhS9ieaDqoxhIqpx_89QAr1YlGu_UO6R6FYl0wDT-nzjyeF5tonSs3FHN0iNIiR3AMOHZu7KUwZaUdHg4eYkU-sQ01QNY_11keHROCRQ", "select_by": "user" }
權杖回應無效
嘗試使用到期、已撤銷或無效的存取權杖提出 API 要求時,來自 Google 的回應示例:
HTTP 回應標頭
www-authenticate: Bearer realm="https://accounts.google.com/", error="invalid_token"
回應主體
{
"error": {
"code": 401,
"message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"errors": [
{
"message": "Invalid Credentials",
"domain": "global",
"reason": "authError",
"location": "Authorization",
"locationType": "header"
}
],
"status": "UNAUTHENTICATED"
}
}