Google+ Platform

Google+ Sign-In for server-side apps

To take advantage of all of the benefits of Google+ Sign-In you must use a hybrid server-side flow where a user authorizes your app on the client side using the JavaScript API client and you send a special one-time authorization code to your server. Your server exchanges this one-time-use code to acquire its own access and refresh tokens from Google for the server to be able to make its own API calls, which can be done while the user is offline. This one-time code flow has security advantages over both a pure server-side flow and over sending access tokens to your server.

The sign-in flow for obtaining an access token for your server-side application is illustrated below.

One-time codes have several security advantages. With codes, Google provides tokens directly to your server without any intermediaries. Although we don't recommend leaking codes, they are very hard to use without your client secret. Keep your client secret secret!

Implementing the one-time-code flow

The Google+ Sign-In button provides both an access token and an authorization code. The code is a one-time code that your server can exchange with Google's servers for an access token.

The following server-side sample code for PHP demonstrates how to do one-time-code flow using the Google API client libraries. Each of the quick-start sample apps also demonstrate the one-time code exchange.

Authenticating Google+ Sign-In with one-time-code flow requires you to:

  1. Create a client ID and client secret.
  2. Create an anti-request forgery state token.
  3. Include the Google+ script on your page.
  4. Add the sign-in button to your page.
  5. Sign in the user.
  6. Send the authorization code to the server.
  7. Confirm the anti-request forgery state token on the server.
  8. Initialize the Google API client library and start the Google+ service.
  9. Run this sample application.

Step 1: Create a client ID and client secret

To create a client ID and client secret, create a Google APIs Console project, enable the Google+ API, and register your JavaScript origins:

  1. Go to the Google Developers Console.
  2. Select a project, or create a new one.
  3. In the sidebar on the left, select APIs & auth. APIs is automatically selected.
  4. In the displayed list of APIs, make sure the Google+ API status is set to ON.
  5. In the sidebar on the left, select Credentials.
  6. In the OAuth section of the page, select Create New Client ID.

    In the resulting Create Client ID dialog box, register the origins where your app is allowed to access the Google APIs. The origin is the unique combination of protocol, hostname, and port.

    1. In the Application type section of the dialog, select Web application.
    2. In the Authorized JavaScript origins field, enter the origin for your app. You can enter multiple origins to allow for your app to run on different protocols, domains, or subdomains. Wildcards are not allowed. In the example below, the second URL could be a production URL.
      http://localhost:8080
      http://myproductionurl.example.com
    3. Select Create Client ID.
    4. In the resulting Client ID for web application panel, note or copy the Client ID and Client secret that your app will need to use to access the APIs.

Step 2: Create an anti-request forgery state token

You must protect the security of your users by preventing request forgery attacks. The first step is creating a unique session token that your client-side code returns alongside the Google generated authorization code. You will later verify this unique session token on your server when a request is made to verify that the user is making the request and not a malicious script. These tokens are often referred to as cross-site request forgery (CSRF) tokens.

The following code demonstrates generating unique session tokens.

PHP

You must also download the Google APIs client library for PHP to use this sample.
  // Create a state token to prevent request forgery.
  // Store it in the session for later validation.
  $state = md5(rand());
  $app['session']->set('state', $state);
  // Set the client ID, token state, and application name in the HTML while
  // serving it.
  return $app['twig']->render('index.html', array(
      'CLIENT_ID' => CLIENT_ID,
      'STATE' => $state,
      'APPLICATION_NAME' => APPLICATION_NAME
  ));
  

Java

You must also download the Google APIs client library for Java to use this sample.
  // Create a state token to prevent request forgery.
  // Store it in the session for later validation.
  String state = new BigInteger(130, new SecureRandom()).toString(32);
  request.session().attribute("state", state);
  // Read index.html into memory, and set the Client ID,
  // Token State, and Application Name in the HTML before serving it.
  return new Scanner(new File("index.html"), "UTF-8")
      .useDelimiter("\\A").next()
      .replaceAll("[{]{2}\\s*CLIENT_ID\\s*[}]{2}", CLIENT_ID)
      .replaceAll("[{]{2}\\s*STATE\\s*[}]{2}", state)
      .replaceAll("[{]{2}\\s*APPLICATION_NAME\\s*[}]{2}",
                  APPLICATION_NAME);
  

Python

You must also download the Google APIs client library for Python to use this sample.
  # Create a state token to prevent request forgery.
  # Store it in the session for later validation.
  state = ''.join(random.choice(string.ascii_uppercase + string.digits)
                  for x in xrange(32))
  session['state'] = state
  # Set the Client ID, Token State, and Application Name in the HTML while
  # serving it.
  response = make_response(
      render_template('index.html',
                      CLIENT_ID=CLIENT_ID,
                      STATE=state,
                      APPLICATION_NAME=APPLICATION_NAME))
  

Step 3: Include the Google+ script on your page

Include the following scripts that demonstrate an anonymous function that inserts a script into the DOM of this index.html web page.

<!-- The top of file index.html -->
<html itemscope itemtype="http://schema.org/Article">
<head>
  <!-- BEGIN Pre-requisites -->
  <script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js">
  </script>
  <script type="text/javascript">
    (function () {
      var po = document.createElement('script');
      po.type = 'text/javascript';
      po.async = true;
      po.src = 'https://plus.google.com/js/client:plusone.js?onload=start';
      var s = document.getElementsByTagName('script')[0];
      s.parentNode.insertBefore(po, s);
    })();
  </script>
  <!-- END Pre-requisites -->
</head>
<!-- ... -->
</html>

Step 4: Add the sign-in button to your page

Add the sign-in button to your web page and set the data-redirecturi to postmessage to enable the one-time-code flow. Replace YOUR_CLIENT_ID with the value that you generated in step 1.

<!-- Add where you want your sign-in button to render -->
<div id="signinButton">
  <span class="g-signin"
    data-scope="https://www.googleapis.com/auth/plus.login"
    data-clientid="YOUR_CLIENT_ID"
    data-redirecturi="postmessage"
    data-accesstype="offline"
    data-cookiepolicy="single_host_origin"
    data-callback="signInCallback">
  </span>
</div>
<div id="result"></div>

Step 5: Sign in the user

The user clicks the sign-in button and grants your app access to the permissions that you requested. The callback function that you specified in the data-callback parameter is passed an authorization result object that contains both the authResult['code'] and authResult['access_token'] if successful.

Step 6: Send the authorization code to the server

The code is your one-time code that your server can exchange for its own access token and refresh token. You can only obtain a refresh token the first time that you perform the code exchange flow. You must store the refresh token that you retrieve for later use because subsequent exchanges will return null for the refresh token. This flow provides increased security over your standard OAuth 2.0 flow.

You can prompt the user to re-authorize your app by adding the data-approvalprompt="force" parameter to your sign-in button. This will show the sign-in button every time the user access the app, and require them to grant access permission. When the button is clicked, the user is presented with the permissions dialog again, and upon authorization your app can exchange the received one-time code for an access token and a new refresh token. The old refresh token will be revoked. You should only include this parameter in limited cases because it causes the authorization dialog to display every time.

Access tokens are always returned with the exchange of a valid authorization code.

The following script defines a callback function for the sign-in button. When a sign-in is successful, the function stores the access token for client-side use and sends the one-time code to your server on the same domain.

<!-- Last part of BODY element in file index.html -->
<script type="text/javascript">
function signInCallback(authResult) {
  if (authResult['code']) {

    // Hide the sign-in button now that the user is authorized, for example:
    $('#signinButton').attr('style', 'display: none');

    // Send the code to the server
    $.ajax({
      type: 'POST',
      url: 'plus.php?storeToken',
      contentType: 'application/octet-stream; charset=utf-8',
      success: function(result) {
        // Handle or verify the server response if necessary.

        // Prints the list of people that the user has allowed the app to know
        // to the console.
        console.log(result);
        if (result['profile'] && result['people']){
          $('#results').html('Hello ' + result['profile']['displayName'] + '. You successfully made a server side call to people.get and people.list');
        } else {
          $('#results').html('Failed to make a server-side call. Check your configuration and console.');
        }
      },
      processData: false,
      data: authResult['code']
    });
  } else if (authResult['error']) {
    // There was an error.
    // Possible error codes:
    //   "access_denied" - User denied access to your app
    //   "immediate_failed" - Could not automatially log in the user
    // console.log('There was an error: ' + authResult['error']);
  }
}
</script>

Next, you will create your server-side page that will accept the one-time code from the client.

Step 7: Confirm the anti-request forgery state token on the server

On the server, you must confirm the token that the client sends to the server matches the token that the server sent to the client. This round-trip verification helps to ensure that the user is making the request and not a malicious script.

The following code demonstrates confirming the session tokens that you previously sent in step 2 to the client:

PHP

You must also download the Google APIs client library for PHP to use this sample.
  // Ensure that this is no request forgery going on, and that the user
  // sending us this connect request is the user that was supposed to.
  if ($request->get('state') != ($app['session']->get('state'))) {
    return new Response('Invalid state parameter', 401);
  }
  

Java

You must also download the Google APIs client library for Java to use this sample.
  // Ensure that this is no request forgery going on, and that the user
  // sending us this connect request is the user that was supposed to.
  if (!request.queryParams("state").equals(
      request.session().attribute("state"))) {
    response.status(401);
    return GSON.toJson("Invalid state parameter.");
  }
  

Python

You must also download the Google APIs client library for Python to use this sample.
  # Ensure that the request is not a forgery and that the user sending
  # this connect request is the expected user.
  if request.args.get('state', '') != session['state']:
    response = make_response(json.dumps('Invalid state parameter.'), 401)
    response.headers['Content-Type'] = 'application/json'
    return response
  

Step 8: Initialize the Google API client library and start the Google+ service

Now that you confirmed the request is not a forgery and you have an authorization code from the client, you can complete the user log in.

PHP

You must also download the Google APIs client library for PHP to use this sample.
  $code = $request->getContent();
  $gPlusId = $request->get['gplus_id'];
  // Exchange the OAuth 2.0 authorization code for user credentials.
  $client->authenticate($code);

  $token = json_decode($client->getAccessToken());
  // Verify the token
  $reqUrl = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' .
          $token->access_token;
  $req = new Google_HttpRequest($reqUrl);

  $tokenInfo = json_decode(
      $client::getIo()->authenticatedRequest($req)->getResponseBody());

  // If there was an error in the token info, abort.
  if ($tokenInfo->error) {
    return new Response($tokenInfo->error, 500);
  }
  // Make sure the token we got is for the intended user.
  if ($tokenInfo->userid != $gPlusId) {
    return new Response(
        "Token's user ID doesn't match given user ID", 401);
  }
  // Make sure the token we got is for our app.
  if ($tokenInfo->audience != CLIENT_ID) {
    return new Response(
        "Token's client ID does not match app's.", 401);
  }

  // Store the token in the session for later use.
  $app['session']->set('token', json_encode($token));
  $response = 'Succesfully connected with token: ' . print_r($token, true);
  

Java

You must also download the Google APIs client library for Java to use this sample.
  String gPlusId = request.queryParams("gplus_id");
  String code = request.body();

  try {
    // Upgrade the authorization code into an access and refresh token.
    GoogleTokenResponse tokenResponse =
        new GoogleAuthorizationCodeTokenRequest(TRANSPORT, JSON_FACTORY,
            CLIENT_ID, CLIENT_SECRET, code, "postmessage").execute();
    // Create a credential representation of the token data.
    GoogleCredential credential = new GoogleCredential.Builder()
        .setJsonFactory(JSON_FACTORY)
        .setTransport(TRANSPORT)
        .setClientSecrets(CLIENT_ID, CLIENT_SECRET).build()
        .setFromTokenResponse(tokenResponse);

    // Check that the token is valid.
    Oauth2 oauth2 = new Oauth2.Builder(
        TRANSPORT, JSON_FACTORY, credential).build();
    Tokeninfo tokenInfo = oauth2.tokeninfo()
        .setAccessToken(credential.getAccessToken()).execute();
    // If there was an error in the token info, abort.
    if (tokenInfo.containsKey("error")) {
      response.status(401);
      return GSON.toJson(tokenInfo.get("error").toString());
    }
    // Make sure the token we got is for the intended user.
    if (!tokenInfo.getUserId().equals(gPlusId)) {
      response.status(401);
      return GSON.toJson("Token's user ID doesn't match given user ID.");
    }
    // Make sure the token we got is for our app.
    if (!tokenInfo.getIssuedTo().equals(CLIENT_ID)) {
      response.status(401);
      return GSON.toJson("Token's client ID does not match app's.");
    }
    // Store the token in the session for later use.
    request.session().attribute("token", tokenResponse.toString());
    return GSON.toJson("Successfully connected user.");
  } catch (TokenResponseException e) {
    response.status(500);
    return GSON.toJson("Failed to upgrade the authorization code.");
  } catch (IOException e) {
    response.status(500);
    return GSON.toJson("Failed to read token data from Google. " +
        e.getMessage());
  }
  

Python

You must also download the Google APIs client library for Python to use this sample.
  gplus_id = request.args.get('gplus_id')
  code = request.data

  try:
    # Upgrade the authorization code into a credentials object
    oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='')
    oauth_flow.redirect_uri = 'postmessage'
    credentials = oauth_flow.step2_exchange(code)
  except FlowExchangeError:
    response = make_response(
        json.dumps('Failed to upgrade the authorization code.'), 401)
    response.headers['Content-Type'] = 'application/json'
    return response

  # Check that the access token is valid.
  access_token = credentials.access_token
  url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s'
         % access_token)
  h = httplib2.Http()
  result = json.loads(h.request(url, 'GET')[1])
  # If there was an error in the access token info, abort.
  if result.get('error') is not None:
    response = make_response(json.dumps(result.get('error')), 500)
    response.headers['Content-Type'] = 'application/json'
    return response
  # Verify that the access token is used for the intended user.
  if result['user_id'] != gplus_id:
    response = make_response(
        json.dumps("Token's user ID doesn't match given user ID."), 401)
    response.headers['Content-Type'] = 'application/json'
    return response
  # Verify that the access token is valid for this app.
  if result['issued_to'] != CLIENT_ID:
    response = make_response(
        json.dumps("Token's client ID does not match app's."), 401)
    response.headers['Content-Type'] = 'application/json'
    return response
  stored_credentials = session.get('credentials')
  stored_gplus_id = session.get('gplus_id')
  if stored_credentials is not None and gplus_id == stored_gplus_id:
    response = make_response(json.dumps('Current user is already connected.'),
                             200)
    response.headers['Content-Type'] = 'application/json'
    return response
  # Store the access token in the session for later use.
  session['credentials'] = credentials
  session['gplus_id'] = gplus_id
  response = make_response(json.dumps('Successfully connected user.', 200))
  

Step 9: Run this sample application

To run this sample application, load the sample/index.html file from steps 3-5:

http://localhost:8080/sample

This web page shows this sign-in button:

On your web page, click on the sign-in button to go through the one-time-code flow by accepting the scope to authorize this app. The server-side code exchanges the one-time code for an access token. The code then queries the people.get and people.list methods to validate access and returns the results of both queries to the client. If the API calls were successful, you will see a success message such as "Hello Your Name".

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.