總覽
如要取得用於呼叫 Google API 的個別使用者存取權杖,Google 提供多個 JavaScript 程式庫:
本指南提供從這些程式庫遷移至 Google Identity 服務程式庫的操作說明。
按照本指南操作,您將可:
- 將已淘汰的平台資料庫替換為 Identity 服務資料庫,
- 如果您使用 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 服務前後的網頁應用程式。
授權碼流程
Google 核發的每個使用者授權碼會傳送至您的後端平台,然後交換為存取權杖和更新權杖。
授權碼流程範例:顯示遷移至身分識別服務前後的網頁應用程式。
在本指南中,請按照粗體字列出的操作說明,新增、移除、更新或取代現有功能。
瀏覽器內網頁應用程式異動
本節將說明在遷移至 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 服務資料庫會取代 gapi.auth2
模組的使用方式。您可以繼續安全地使用 適用於 JavaScript 的 Google API 用戶端程式庫中的 gapi.client
模組,並利用其自動建立可呼叫的 JS 方法、批次處理多個 API 呼叫,以及 CORS 管理功能的優勢。
Cookie
使用者授權不需要使用 Cookie。
如要進一步瞭解使用者驗證如何使用 Cookie,請參閱「從 Google 登入遷移」一文,以及「Google 如何使用 Cookie」一文,瞭解其他 Google 產品和服務如何使用 Cookie。
憑證
Google Identity Services 會將使用者驗證和授權分為兩個不同的作業,並將使用者憑證分開:用於識別使用者的 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 服務:
物件:
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
。
授權碼流程
按照「初始化 Code 用戶端」中的範例,新增 CodeClientConfig
物件和 initCodeClient()
呼叫,以設定網路應用程式。
從隱含流程切換至授權碼流程時,請注意下列事項:
移除 Google 登入 JavaScript 用戶端參考資料
物件:
gapi.auth2.AuthorizeConfig
方法:
gapi.auth2.init()
參數:
gapi.auth2.AuthorizeConfig.login_hint
gapi.auth2.GoogleUser.getHostedDomain()
權杖要求
使用者手勢 (例如按下按鈕) 會產生要求,導致存取權存證直接透過隱含流程傳回使用者的瀏覽器,或是在使用者授權碼換取存取權存證和重新整理權證後,傳回後端平台。
隱含流程
當使用者登入並與 Google 建立有效工作階段時,您可以在瀏覽器中取得存取權權杖並加以使用。在隱含模式中,即使先前已提出要求,使用者仍必須透過手勢要求存取權存取權。
取代 Google 登入 JavaScript 用戶端參考資料:使用 Google 身分識別服務:
方法:
gapi.auth2.authorize()
(使用TokenClient.requestAccessToken()
付款)GoogleUser.reloadAuthResponse()
搭配TokenClient.requestAccessToken()
新增連結或按鈕來呼叫 requestAccessToken()
,以便啟動彈出式使用者體驗流程,要求存取權杖,或在現有權杖到期時取得新權杖。
將程式碼集更新為:
- 使用
requestAccessToken()
觸發 OAuth 2.0 權杖流程。 - 使用
requestAccessToken
和OverridableTokenClientConfig
,將多個範圍的單一要求分割為多個較小的要求,以支援增量授權。 - 在現有權杖過期或遭到撤銷時要求新權杖。
使用多個範圍可能需要對程式碼庫進行結構變更,以便在需要時要求存取範圍,而非一次要求所有範圍,這稱為漸進式授權。每個要求都應該包含盡可能少的範圍,最好是單一範圍。如要進一步瞭解如何更新應用程式以進行漸進式授權,請參閱如何處理使用者同意聲明。
存取權杖到期時,gapi.auth2
模組會自動為網頁應用程式取得新的有效存取權杖。為了提升使用者安全性,Google Identity 服務程式庫不支援這個自動權杖重新整理程序。您必須更新網頁應用程式,才能偵測過期的存取權杖,並索取新的權杖。詳情請參閱下方的「符記處理」一節。
授權碼流程
新增連結或按鈕來呼叫 requestCode()
,以便向 Google 索取授權碼。如需範例,請參閱「觸發 OAuth 2.0 授權碼流程」。
如要進一步瞭解如何回應已過期或已撤銷的存取權存取權,請參閱下方的「處理權杖」一節。
權杖處理
新增錯誤處理功能,以便在使用已過期或已撤銷的存取權杖時,偵測 Google API 呼叫失敗,並要求新的有效存取權杖。
使用已過期或已撤銷的存取權權杖時,Google API 會傳回 HTTP 狀態碼 401 Unauthorized
和 invalid_token
錯誤訊息。如需範例,請參閱「無效的權杖回應」。
過期的權杖
存取權杖的有效期限很短,通常只有幾分鐘。
撤銷權杖
Google 帳戶擁有者隨時可以撤銷先前同意的授權。這麼做會使現有的存取權杖和更新權杖失效。系統可能會使用 revoke()
或透過 Google 帳戶從您的平台觸發撤銷程序。
取代 Google 登入 JavaScript 用戶端參考資料:使用 Google 身分識別服務:
方法:
getAuthInstance().disconnect()
(使用google.accounts.oauth2.revoke()
付款)GoogleUser.disconnect()
(使用google.accounts.oauth2.revoke()
付款)
當使用者在您的平台上刪除自己的帳戶時,請呼叫 revoke
,或要求移除與應用程式共用資料的同意聲明。
使用者同意聲明提示
如果您的網頁應用程式或後端平台要求存取權存取權杖,Google 就會向使用者顯示同意聲明對話方塊。請參閱 Google 向使用者顯示的同意聲明對話方塊範例。
向應用程式發出存取權權杖前,您必須先建立現有的有效 Google 工作階段,才能提示使用者同意並記錄結果。如果使用者尚未建立現有工作階段,系統可能會要求使用者登入 Google 帳戶。
使用者登入
使用者可以在不同的瀏覽器分頁中登入 Google 帳戶,也可以透過瀏覽器或作業系統登入。建議您在網站中加入「使用 Google 帳戶登入」功能,這樣一來,當使用者首次開啟應用程式時,系統就能在 Google 帳戶和瀏覽器之間建立有效的工作階段。這樣做可帶來以下好處:
- 盡量減少使用者必須登入的次數,如果尚未有有效的工作階段,要求存取權存證就會啟動 Google 帳戶登入程序。
- 直接使用 JWT ID 權杖憑證
email
欄位,做為CodeClientConfig
或TokenClientConfig
物件中login_hint
參數的值。如果您的平台未維護使用者帳戶管理系統,這項功能就特別實用。 - 查詢 Google 帳戶,並將其與您平台上的現有本機使用者帳戶建立關聯,以盡量減少平台上的重複帳戶。
- 建立新的本機帳戶時,註冊對話方塊和流程可以與使用者驗證對話方塊和流程明確區隔,減少所需步驟數並提高完成率。
在登入後,且在發出存取權存取金鑰之前,使用者必須針對應用程式要求的範圍提供同意聲明。
權杖和同意回應
取得同意聲明後,系統會傳回存取權杖,以及使用者核准或拒絕的權限清單。
精細權限可讓使用者核准或拒絕個別範圍。要求存取多個範圍時,系統會獨立授予或拒絕每個範圍,不受其他範圍影響。根據使用者的選擇,應用程式會依個別範圍有選擇地啟用功能。
隱含流程
取代 Google 登入 JavaScript 用戶端參照資料,改用 Google Identity 服務:
物件:
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.auth2
模組會自動載入並由 gapi.client.init()
使用,因此會隱藏。
<!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 服務 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 的 async/await
本範例說明如何使用權杖模型新增 Google Identity Service 程式庫、移除 gapi.auth2
模組,以及使用 JavaScript 專用 Google API 用戶端程式庫呼叫 API。
Promise、async 和 await 是用來強制執行程式庫載入順序,以及擷取及重試授權錯誤。API 呼叫只有在有效的存取權杖可用時才會發出。
當使用者首次載入網頁時缺少存取權權杖,或在存取權權杖到期後,使用者應按下「Show Calendar」(顯示行事曆) 按鈕。
<!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 服務程式庫、移除 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 回呼處理常式,將回應代理至您的平台。無論是哪一種情況,您的後端平台都會完成 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 服務 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 Redirect UX
授權碼模型支援彈出式和重新導向 UX 模式,可將每位使用者的授權碼傳送至平台代管的端點。重新導向使用者體驗模式如下所示:
<!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 服務時的建議做法:
現有的 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() or requestAccessToken() | 以新內容取代舊內容 |
GoogleUser.getBasicProfile() | ,即可逐一移除離線觀看清單內的影片;請改用 ID 權杖,詳情請參閱「從 Google 登入遷移」一文。 | |
GoogleUser.getGrantedScopes() | hasGrantedAnyScope() | 以新內容取代舊內容 |
GoogleUser.getHostedDomain() | 移除 | |
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.authorize() | requestCode() or requestAccessToken() | 以新內容取代舊內容 |
gapi.auth2.ClientConfig() | TokenClientConfig 或 CodeClientConfig | 以新內容取代舊內容 |
gapi.auth2.getAuthInstance() | 移除 | |
gapi.auth2.init() | initTokenClient() or initCodeClient() | 以新內容取代舊內容 |
gapi.auth2.OfflineAccessOptions 物件 | 移除 | |
gapi.auth2.SignInOptions 物件 | 移除 | |
gapi.signin2 物件和相關方法: | ||
gapi.signin2.render() | ,即可逐一移除離線觀看清單內的影片;使用 HTML DOM 載入 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"
}
}