Overview
To obtain a per user access token to call Google APIs, Google offers multiple JavaScript libraries:
This guide provides instructions to migrate from these libraries to the Google Identity Services library.
By following this guide, you will:
- replace the deprecated Platform Library with the Identity Services library, and
- if using the API Client Library, remove the deprecated
gapi.auth2
module, its methods and objects, replacing them with Identity Services equivalents.
For a description of what has changed with the Identity Services JavaScript library read the overview and how user authorization works to review key terms and concepts.
If you are looking for authentication for user sign-up and sign-in see Migrating from Google Sign-In instead.
Identify your authorization flow
There are two possible user authorization flows: implicit and authorization code.
Review your web app to identify the type of authorization flow currently being used.
Indications your web app is using the implicit flow:
- Your web app is purely browser based, with no backend platform.
- The user must be present to call Google APIs, your app uses only access tokens, and does not require refresh tokens.
- Your web app loads
apis.google.com/js/api.js
. - Your implementation is based upon OAuth 2.0 for Client-side Web Applications.
- Your app uses either the
gapi.client
orgapi.auth2
modules found in Google API Client Library for JavaScript.
Indications your web app is using the authorization code flow:
Your implementation is based upon:
Your app executes both in the user's browser, and on your backend platform.
Your backend platform hosts an authorization code endpoint.
Your backend platform calls Google APIs on behalf of users without requiring them to be present, also known as offline mode.
Refresh tokens are managed and stored by your backend platform.
In some cases, your codebase might support both flows.
Choose an authorization flow
Prior to beginning your migration you need to determine if continuing with your existing flow or adopting a different flow best meets your needs.
Review choosing an authorization flow to understand the key differences and tradeoffs between the two flows.
In most cases, the authorization code flow is recommended as it offers the highest level of user security. Implementing this flow also enables your platform to more easily add new offline functionalities such as fetching updates to notify users of notable changes to their calendar, photos, subscriptions, and so on.
Choose an authorization flow using the selectors below.
Implicit flow
Obtain an access token for in-browser use while the user is present.
Implicit flow examples shows web apps before and after migration to Identity Services.
Authorization code flow
A per user authorization code issued by Google is delivered to your backend platform, where it is then exchanged for an access token and refresh token.
Authorization code flow examples shows web apps before and after migration to Identity Services.
Throughout this guide, follow the instructions listed in bold to Add, Remove, Update, or Replace existing functionality.
Changes to your in-browser web app
This section reviews the changes you will make to your in-browser web app when migrating to the Google Identity Services JavaScript library.
Identifying affected code and testing
A debug cookie can help to locate affected code and to test post-deprecation behavior.
In large or complex apps, it may be difficult to find all code affected by the
deprecation of the gapi.auth2
module. To log existing use of soon to be
deprecated functionality to the console, set the value of the
G_AUTH2_MIGRATION
cookie to informational
. Optionally, add a colon followed
by a key value to also log to session storage. After sign-in
and receipt of credentials review or send collected logs to a backend for later
analysis. For example, informational:showauth2use
saves origin and URL to a
session storage key named showauth2use
.
To verify app behavior when the gapi.auth2
module is no longer loaded, set the
value of the G_AUTH2_MIGRATION
cookie to enforced
. This enables testing of
post-deprecation behavior in advance of the enforcement date.
Possible G_AUTH2_MIGRATION
cookie values:
enforced
Do not loadgapi.auth2
module.informational
Log use of deprecated functionality to JS console. Also log to session storage when an optional key name is set:informational:key-name
.
To minimize user impact it is recommended that you first set this cookie locally during development and test, before using it in production environments.
Libraries and modules
The gapi.auth2
module manages user authentication for sign-in and the implicit
flow for authorization, replace this deprecated module, and its objects and
methods with the Google Identity Services library.
Add the Identity Services library to your web app by including it in your document:
<script src="https://accounts.google.com/gsi/client" async defer></script>
Remove any instances of loading the auth2
module using gapi.load('auth2',
function)
.
The Google Identity Services library replaces usage of the gapi.auth2
module.
You can safely continue using the gapi.client
module from the Google API
Client Library for JavaScript, and take advantage of its automatic creation
of callable JS methods from a discovery document, batching multiple API calls,
and CORS management functionality.
Cookies
User authorization does not require the use of cookies.
See Migrating from Google Sign-In for details of how user authentication makes use of cookies, and How Google uses cookies for cookie use by other Google products and services.
Credentials
Google Identity Services separates user authentication and authorization into two distinct operations, and user credentials are separate: the ID token used to identify a user is returned separately from the access token used for authorization.
To view these changes, see example credentials.
Implicit flow
Separate user authentication and authorization by removing user profile handling from authorization flows.
Remove these Google Sign-In JavaScript client references:
Methods
GoogleUser.getBasicProfile()
GoogleUser.getId()
Authorization code flow
Identity Services separates in-browser credentials into ID token and access token. This change does not apply to credentials obtained through direct calls to Google OAuth 2.0 endpoints from your backend platform or through libraries running on a secure server on your platform such as the Google APIs Node.js Client.
Session state
Previously, Google Sign-In helped you to manage user signed-in status using:
- Callback handlers for Monitoring the user's session state.
- Listeners for events and changes to signed-in status for a user's Google Account.
You are responsible for managing sign-in state and user sessions to your web app.
Remove these Google Sign-In JavaScript client references:
Objects:
gapi.auth2.SignInOptions
Methods:
GoogleAuth.attachClickHandler()
GoogleAuth.isSignedIn()
GoogleAuth.isSignedIn.get()
GoogleAuth.isSignedIn.listen()
GoogleAuth.signIn()
GoogleAuth.signOut()
GoogleAuth.currentUser.get()
GoogleAuth.currentUser.listen()
GoogleUser.isSignedIn()
Client configuration
Update your web app to initialize a token client for the implicit or authorization code flow.
Remove these Google Sign-In JavaScript client references:
Objects:
gapi.auth2.ClientConfig
gapi.auth2.OfflineAccessOptions
Methods:
gapi.auth2.getAuthInstance()
GoogleUser.grant()
Implicit flow
Add a TokenClientConfig
object and initTokenClient()
call to
configure your web app, following the example in initialize a token
client.
Replace Google Sign-In JavaScript client references with Google Identity Services:
Objects:
gapi.auth2.AuthorizeConfig
withTokenClientConfig
Methods:
gapi.auth2.init()
withgoogle.accounts.oauth2.initTokenClient()
Parameters:
gapi.auth2.AuthorizeConfig.login_hint
withTokenClientConfig.login_hint
.gapi.auth2.GoogleUser.getHostedDomain()
withTokenClientConfig.hd
.
Authorization code flow
Add a CodeClientConfig
object and initCodeClient()
call to configure
your web app, following the example in initialize a Code Client.
When switching from the implicit to the authorization code flow:
Remove Google Sign-In JavaScript client references
Objects:
gapi.auth2.AuthorizeConfig
Methods:
gapi.auth2.init()
Parameters:
gapi.auth2.AuthorizeConfig.login_hint
gapi.auth2.GoogleUser.getHostedDomain()
Token request
A user gesture, such as a button click, generates a request that results in an access token being returned directly to the user's browser with the implicit flow, or to your backend platform after exchanging a per user authorization code for an access token and refresh token.
Implicit flow
Access tokens may be obtained and used in-browser while the user is signed-in and has an active session with Google. For implicit mode, a user gesture is required to request an access token, even if there was a prior request.
Replace Google Sign-In JavaScript client references: with Google Identity Services:
Methods:
gapi.auth2.authorize()
withTokenClient.requestAccessToken()
GoogleUser.reloadAuthResponse()
withTokenClient.requestAccessToken()
Add a link or button to call requestAccessToken()
to initiate the
pop-up UX flow to request an access token, or to obtain a new token when the
existing token expires.
Update your codebase to:
- Trigger the OAuth 2.0 token flow with
requestAccessToken()
. - Support incremental authorization by using
requestAccessToken
andOverridableTokenClientConfig
to separate one request for many scopes into multiple smaller requests. - Request a new token when the existing token expires, or is revoked.
Working with multiple scopes may require structural changes to your codebase to request access to scopes only as they are needed rather than all at once, this is known as incremental authorization. Each request should contain as few scopes as possible, and ideally a single scope. See how to handle user consent for more on how to update your app for incremental authorization.
When an access token expires, the gapi.auth2
module automatically obtains
a new, valid access token for your web app. For improved user security, this
automatic token refresh process is not supported by the Google Identity
Services library. Your web app must be updated to detect an expired access
token and request a new one. See the Token handling section below for more.
Authorization code flow
Add a link or button to call requestCode()
to request an authorization
code from Google. For an example, see Trigger OAuth 2.0 Code Flow.
See the Token handling section below for more on how to respond to an expired or revoked access token.
Token handling
Add error handling to detect failed Google API calls when an expired or revoked access token is used, and to request a new, valid access token.
An HTTP status code of 401 Unauthorized
and invalid_token
error message is
returned by Google APIs when an expired or revoked access token is used. For an
example, see Invalid token response.
Expired tokens
Access tokens are short-lived, and often valid only for a few minutes.
Token revocation
At any time, a Google Account owner may revoke previously granted consent. Doing
so invalidates existing access tokens and refresh tokens. Revocation may be
triggered from your platform using revoke()
or through a Google
Account.
Replace Google Sign-In JavaScript client references: with Google Identity Services:
Methods:
getAuthInstance().disconnect()
withgoogle.accounts.oauth2.revoke()
GoogleUser.disconnect()
withgoogle.accounts.oauth2.revoke()
Call revoke
when a user deletes their account on your platform, or
wishes to remove consent to share data with your app.
User consent prompt
Google displays a consent dialog to the user when either your web app or backend platform requests an access token. See example consent dialogs displayed by Google to users.
Prior to issuing an access token to your app, an existing and active Google session is required to prompt for user consent and record the result. The user may be required to sign-in to a Google Account if an existing session has not already been established.
User sign-in
Users may be signed into a Google Account in a separate browser tab, or natively through a browser or operating system. We recommend adding Sign In With Google to your site to establish an active session between a Google Account and the browser when the user first opens your app. Doing so offers these benefits:
- Minimizes the number of times a user must sign-in, requesting an access token initiates the Google Account sign-in process if an active session does not already exist.
- Direct use the JWT ID Token credential
email
field as the value of thelogin_hint
parameter inCodeClientConfig
orTokenClientConfig
objects. This is especially helpful if your platform does not maintain a user account management system. - Lookup and associate a Google Account with an existing local user account on your platform, helping to minimize duplicate accounts on your platform.
- When a new, local account is created, your sign-up dialogs and flow can be clearly separated from user authentication dialogs and flows, reducing the number of required steps and improving drop-off rate.
After sign-in, and before an access token is issued, users must provide consent for your application for the requested scopes.
Token and consent response
After consent, an access token is returned along with a list of scopes approved or rejected by the user.
Granular permissions allow users to approve or deny individual scopes. When requesting access to multiple scopes, each scope is granted or rejected independent of the other scopes. Based upon user choice your app selectively enables features and functionality which depend upon an individual scope.
Implicit flow
Replace Google Sign-In JavaScript client references with Google Identity Services:
Objects:
gapi.auth2.AuthorizeResponse
withTokenClient.TokenResponse
gapi.auth2.AuthResponse
withTokenClient.TokenResponse
Methods:
GoogleUser.hasGrantedScopes()
withgoogle.accounts.oauth2.hasGrantedAllScopes()
GoogleUser.getGrantedScopes()
withgoogle.accounts.oauth2.hasGrantedAllScopes()
Remove Google Sign-In JavaScript client references:
Methods:
GoogleUser.getAuthResponse()
Update your web app with hasGrantedAllScopes()
and
hasGrantedAnyScope()
by following this granular permissions example.
Authorization code flow
Update or Add an authorization code endpoint to your backend platform by following the instructions in auth code handling.
Update your platform to follow the steps described in the Use Code Model guide to validate the request and obtain an access token and refresh token.
Update your platform to selectively enable or disable features and functionalities based upon the individual scopes the user has approved by following the instructions for incremental authorization and examine scopes of access granted by the user.
Implicit flow examples
The old way
GAPI Client Library
Example of the Google API Client Library for JavaScript running in browser using a popup dialog for user consent.
The gapi.auth2
module is automatically loaded and used by
gapi.client.init()
, and so is hidden.
<!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 Client Library
OAuth 2.0 for Client-side Web Applications running in browser using a popup dialog for user consent.
The gapi.auth2
module is loaded manually.
<!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 Endpoints
OAuth 2.0 for Client-side Web Applications running in browser using redirects to Google for user consent.
This example shows direct calls to Google's OAuth 2.0 endpoints from the
user's browser and does not use the gapi.auth2
module or a JavaScript
library.
<!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>
The new way
GIS only
This example shows only the Google Identity Service JavaScript library using the token model and popup dialog for user consent. It is provided to illustrate the minimal number of steps required to configure a client, request and obtain an access token, and to call a 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
This example shows how to add the Google Identity Service library using the
token model, remove the gapi.auth2
module, and call an API using the
Google API Client Library for JavaScript.
Promises, async and await are used to enforce library loading order and to catch and retry authorization errors. An API call is made only after a valid access token is available.
Users are expected to press the 'Show Calendar' button when the access token is missing when the page is first loaded, or later after the access token has expired.
<!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 callback
This example shows how to add the Google Identity Service library using the
token model, remove the gapi.auth2
module, and call an API using the
Google API Client Library for JavaScript.
Variables are used to enforce library loading order. GAPI calls are made from the within the callback after a valid access token is returned.
Users are expected to press the Show Calendar button when the page is first loaded and again when they'd like to refresh their Calendar info.
<!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>
Authorization code flow examples
The Google Identity Service library pop-up UX can either use a URL redirect to return an authorization code directly to your backend token endpoint, or a JavaScript callback handler running in the user's browser which proxies the response to your platform. In either case, your backend platform will complete the OAuth 2.0 flow to obtain a valid refresh and access token.
The old way
Server-side Web Apps
Google Sign-In for server-side apps running in on backend platform using a redirect to Google for user consent.
<!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 using redirect
Using OAuth 2.0 for Web Server Applications to send authorization code from the user's browser to your backend platform. User consent handled by redirecting the user's browser to 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();
}
The new way
GIS Popup UX
This example shows only the Google Identity Service JavaScript library using the authorization code model a popup dialog for user consent and callback handler to receive the authorization code from Google. It is provided to illustrate the minimal number of steps required to configure a client, obtain consent and send an authorization code to your backend platform.
<!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
Authorization code model supports the popup and redirect UX modes to send a per user authorization code to the endpoint hosted by your platform. The redirect UX mode is shown here:
<!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 libraries
Google Identity Services is a single JavaScript library used for user authentication and authorization that consolidates and replaces features and functionality found in multiple different libraries and modules:
Actions to take when migrating to Identity Services:
Existing JS library | New JS library | Notes |
---|---|---|
apis.google.com/js/api.js |
accounts.google.com/gsi/client |
Add new library and follow the implicit flow. |
apis.google.com/js/client.js |
accounts.google.com/gsi/client |
Add new library and the authorization code flow. |
Library quick reference
Object and method comparison between the Old Google Sign-In JavaScript client library and the New Google Identity Services library and Notes with additional information and action to take during migration.
Old | New | Notes |
---|---|---|
GoogleAuth object and associated methods: | ||
GoogleAuth.attachClickHandler() | Remove | |
GoogleAuth.currentUser.get() | Remove | |
GoogleAuth.currentUser.listen() | Remove | |
GoogleAuth.disconnect() | google.accounts.oauth2.revoke | Replace old with new. Revocation may also occur from https://myaccount.google.com/permissions |
GoogleAuth.grantOfflineAccess() | Remove, follow the authorization code flow. | |
GoogleAuth.isSignedIn.get() | Remove | |
GoogleAuth.isSignedIn.listen() | Remove | |
GoogleAuth.signIn() | Remove | |
GoogleAuth.signOut() | Remove | |
GoogleAuth.then() | Remove | |
GoogleUser object and associated methods: | ||
GoogleUser.disconnect() | google.accounts.id.revoke | Replace old with new. Revocation may also occur from https://myaccount.google.com/permissions |
GoogleUser.getAuthResponse() | requestCode() or requestAccessToken() | Replace old with new |
GoogleUser.getBasicProfile() | Remove. Use ID Token instead, see Migrating from Google Sign-In. | |
GoogleUser.getGrantedScopes() | hasGrantedAnyScope() | Replace old with new |
GoogleUser.getHostedDomain() | Remove | |
GoogleUser.getId() | Remove | |
GoogleUser.grantOfflineAccess() | Remove, follow the authorization code flow. | |
GoogleUser.grant() | Remove | |
GoogleUser.hasGrantedScopes() | hasGrantedAnyScope() | Replace old with new |
GoogleUser.isSignedIn() | Remove | |
GoogleUser.reloadAuthResponse() | requestAccessToken() | Remove old, call new to replace expired or revoked access token. |
gapi.auth2 object and associated methods: | ||
gapi.auth2.AuthorizeConfig object | TokenClientConfig or CodeClientConfig | Replace old with new |
gapi.auth2.AuthorizeResponse object | Remove | |
gapi.auth2.AuthResponse object | Remove | |
gapi.auth2.authorize() | requestCode() or requestAccessToken() | Replace old with new |
gapi.auth2.ClientConfig() | TokenClientConfig or CodeClientConfig | Replace old with new |
gapi.auth2.getAuthInstance() | Remove | |
gapi.auth2.init() | initTokenClient() or initCodeClient() | Replace old with new |
gapi.auth2.OfflineAccessOptions object | Remove | |
gapi.auth2.SignInOptions object | Remove | |
gapi.signin2 object and associated methods: | ||
gapi.signin2.render() | Remove. HTML DOM loading of the g_id_signin element or JS call to google.accounts.id.renderButton triggers user sign-in to a Google Account. |
Example credentials
Existing credentials
The Google Sign-In platform library, Google API Client Library for JavaScript, or direct calls to Google Auth 2.0 endpoints return both an OAuth 2.0 access token and an OpenID Connect ID Token in a single response.
Example response containing both access_token
and 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 Services credential
The Google Identity Services library returns:
either an access token when used for authorization:
{ "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" }
or, an ID token when used for authentication:
{ "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" }
Invalid token response
Example response from Google when attempting to make an API request using an expired, revoked, or invalid access token:
HTTP Response Headers
www-authenticate: Bearer realm="https://accounts.google.com/", error="invalid_token"
Response body
{
"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"
}
}