Using the token model

The google.accounts.oauth2 JavaScript library helps you prompt for user consent and obtain an access token to work with user data. It is based upon the OAuth 2.0 implicit grant flow and designed to allow you to either call Google APIs directly using REST and CORS, or to use our Google APIs client library for JavaScript (also known as gapi.client) for simple, flexible access to our more complex APIs.

Before accessing protected user data from a browser, users on your site trigger Google's web based account chooser, sign-in, and consent processes, and lastly Google's OAuth servers issue and return an access token to your web app.

In the token based authorization model, there is no need to store per-user refresh tokens on your backend server.

It is recommended that you follow the approach outlined here instead of the techniques covered by the older OAuth 2.0 for Client-side Web Applications guide.

Setup

Find or create a client ID by following the steps described in the Get your Google API client ID guide. Next, add the client library to the pages on your site that will be calling Google APIs. Lastly, initialize the token client. Typically, this is done within the client library's onload handler.

Initialize a token client

Call initTokenClient() to initialize a new token client with your web app's client ID, optionally you may include a list of one or more scopes the user needs to access:

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  callback: (response) => {
    ...
  },
});

Trigger the OAuth 2.0 token flow

Use the requestAccessToken() method to trigger the token UX flow and obtain an access token. Google prompts the user to:

  • Choose their account,
  • sign-in to the Google Account if not already signed-in,
  • grant consent for your web app to access each requested scope.

A user gesture triggers the token flow: <button onclick="client.requestAccessToken();">Authorize me</button>

Google then returns a TokenResponse containing an access token and list of scopes the user has granted access to, or an error, to your callback handler.

Users may close the account chooser or sign-in windows, in which case your callback function will not be invoked.

The design and user experience for your app should be implemented only after a thorough review of Google's OAuth 2.0 Policies. These policies cover working with multiple scopes, when and how to handle user consent, and more.

Incremental authorization is a policy and app design methodology used to request access to resources, using scopes, only as needed rather than up-front and all at once. Users may approve or reject sharing of the individual resources requested by your app, this is known as granular permissions.

During this process Google prompts for user consent, individually listing each requested scope, users select the resources to be shared with your app, and lastly, Google invokes your callback function to return an Access token and user approved scopes. Your app then safely handles the various different outcomes possible with granular permissions.

Incremental authorization

For web apps, the following two high-level scenarios demonstrate incremental authorization using:

  • A single-page Ajax app, often using XMLHttpRequest with dynamic access to resources.
  • Multiple web-pages, resources are separated and managed on a per page basis.

These two scenarios are presented to illustrate design considerations and methodologies, but are not intended to be comprehensive recommendations on how to build consent into your app. Real-world apps may use a variation or combination of these techniques.

Ajax

Add support for incremental authorization to your app by making multiple calls to requestAccessToken() and using the OverridableTokenClientConfig object's scope parameter to request individual scopes at the time they are needed and only when necessary. In this example resources will be requested and visible only after a user gesture expands a collapsed content section.

Ajax app
Initialize the token client on page load:
        const client = google.accounts.oauth2.initTokenClient({
          client_id: 'YOUR_GOOGLE_CLIENT_ID',
          callback: "onTokenResponse",
        });
      
Request consent and obtain access tokens through user gestures, click `+` to open:

Docs to read

Show recent documents

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/documents.readonly'
             })
           );
        

Upcoming events

Show calendar info

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/calendar.readonly'
             })
           );
        

Display photos

          client.requestAccessToken(
            overrideConfig = ({
               scope = 'https://www.googleapis.com/auth/photoslibrary.readonly'
             })
           );
        

Each call to requestAccessToken triggers a user consent moment, your app will have access only to those resources required by the section a user chooses to expand, thus limiting resource sharing through user choice.

Multiple web-pages

When designing for incremental authorization, multiple pages are used to request only the scope(s) required to load a page, reducing complexity and the need to make multiple calls to obtain user consent and retrieve an access token.

Multi-page app
Web page Code
Page 1. Docs to read
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/documents.readonly',
  });
  client.requestAccessToken();
          
Page 2. Upcoming events
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/calendar.readonly',
  });
  client.requestAccessToken();
          
Page 3. Photo carousel
  const client = google.accounts.oauth2.initTokenClient({
    client_id: 'YOUR_GOOGLE_CLIENT_ID',
    callback: "onTokenResponse",
    scope: 'https://www.googleapis.com/auth/photoslibrary.readonly',
  });
  client.requestAccessToken();
          

Each page requests the necessary scope and obtains an access token by calling initTokenClient() and requestAccessToken() at load time. In this scenario, individual web pages are used to clearly separate user functionality and resources by scope. In a real-world situation, individual pages may request multiple related scopes.

Granular permissions

Granular permissions are handled the same way in all scenarios; after requestAccessToken() invokes your callback function and an access token returned, check that the user has approved the requested scopes using hasGrantedAllScopes() or hasGrantedAnyScope(). For example:

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly \
          https://www.googleapis.com/auth/documents.readonly \
          https://www.googleapis.com/auth/photoslibrary.readonly',
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) {
      if (google.accounts.oauth2.hasGrantedAnyScope(tokenResponse,
          'https://www.googleapis.com/auth/photoslibrary.readonly')) {
        // Look at pictures
        ...
      }
      if (google.accounts.oauth2.hasGrantedAllScopes(tokenResponse,
          'https://www.googleapis.com/auth/calendar.readonly',
          'https://www.googleapis.com/auth/documents.readonly')) {
        // Meeting planning and review documents
        ...
      }
    }
  },
});

Any previously accepted grants from prior sessions or requests will also be included in the response. A record of user consent is maintained per user and Client ID, and persists across multiple calls to initTokenClient() or requestAccessToken(). By default, user consent is only necessary the first time a user visits your website and requests a new scope but may be requested on every page load using prompt=consent in Token Client config objects.

Working with tokens

In the Token model, an access token is not stored by the OS or browser, instead a new token is first obtained at page load time, or subsequently by triggering a call to requestAccessToken() through a user gesture such as a button press.

Using REST and CORS with Google APIs

An access token can be used to make authenticated requests to Google APIs using REST and CORS. This enables users to sign-in, grant consent, Google to issue an access token and your site to work with the user's data.

In this example, view the signed-in users upcoming calendar events using the access token returned by tokenRequest():

var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://www.googleapis.com/calendar/v3/calendars/primary/events');
xhr.setRequestHeader('Authorization', 'Bearer ' + tokenResponse.access_token);
xhr.send();

See How to use CORS to access Google APIs for more.

The next section covers how to easily integrate with more complex APIs.

Working with the Google APIs JavaScript library

The token client works with the Google API Client Library for JavaScript See the code snippet below.

const client = google.accounts.oauth2.initTokenClient({
  client_id: 'YOUR_GOOGLE_CLIENT_ID',
  scope: 'https://www.googleapis.com/auth/calendar.readonly',
  callback: (tokenResponse) => {
    if (tokenResponse && tokenResponse.access_token) {
      gapi.client.setApiKey('YOUR_API_KEY');
      gapi.client.load('calendar', 'v3', listUpcomingEvents);
    }
  },
});

function listUpcomingEvents() {
  gapi.client.calendar.events.list(...);
}

Token expiration

By design, access tokens have a short lifetime. If the access token expires prior to the end of the user's session, obtain a new token by calling requestAccessToken() from a user-driven event such as a button press.

Call the google.accounts.oauth2.revoke method to remove user consent and access to resources for all of the scopes granted to your app. A valid access token is required to revoke this permission:

google.accounts.oauth2.revoke('414a76cb127a7ece7ee4bf287602ca2b56f8fcbf7fcecc2cd4e0509268120bd7', done => {
    console.log(done);
    console.log(done.successful);
    console.log(done.error);
    console.log(done.error_description);
  });