3
votes

UPDATE 27th January 2013

I have now resolved this, Please check the accepted answer.


I am having trouble to get my refresh token and my access token when using the server side flow between my Android Application and my PHP server.

So I have managed to get my One Time Code by using the below:

AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
    @Override
    protected String doInBackground(Void... params) {

        Bundle appActivities = new Bundle();
        appActivities.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES,
                "http://schemas.google.com/AddActivity");
        String scopes = "oauth2:server:client_id:" + SERVER_CLIENT_ID + 
                ":api_scope:" + SCOPE_STRING;

        try {
            code = GoogleAuthUtil.getToken(
                    OneTimeCodeActivity.this,         // Context context
                    mPlusClient.getAccountName(),     // String accountName
                    scopes,                           // String scope
                    appActivities                     // Bundle bundle
            );
        } catch (IOException transientEx) {
            // network or server error, the call is expected to succeed if you try again later.
            // Don't attempt to call again immediately - the request is likely to
            // fail, you'll hit quotas or back-off.
            System.out.println(transientEx.printStactTrace());
            return "Error";
        } catch (UserRecoverableAuthException e) {
            // Recover
            code = null;
            System.out.println(e.printStackTrace());
            OneTimeCodeActivity.this.startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
        } catch (GoogleAuthException authEx) {
            // Failure. The call is not expected to ever succeed so it should not be
            // retried.
            System.out.println(authEx.printStackTrace());
            return "Error";
        } catch (Exception e) {
            System.out.println(authEx.printStackTrace());
        }
   }

Which will then store the token in the variable "code" and I call up the async task as

task.execute();

The code above will always bring up a popup message and throw UserRecoverableAuthException Need Permission that requires the user to grant offline access, which means the above will need to be called twice to retrieve the code and store it in "code"

I am now trying to send this across to my server which is implemented in PHP.

I have used the quick start https://developers.google.com/+/quickstart/php and managed to get that working.

In here, there is a sample signin.php

In here and according to the documentation this already implements a One Time Authorisation Server Side Flow.

So now my problem is sending this One Time Code to the server.

I used the photohunt Android Auth example for this located here.

https://github.com/googleplus/gplus-photohunt-client-android/blob/master/src/com/google/plus/samples/photohunt/auth/AuthUtil.java

I used the "authorization" method of the code and called up signin.php/connect through a post method shown below

$app->post('/connect', function (Request $request) use ($app, $client) {
    $token = $app['session']->get('token');

    if (empty($token)) {
        // 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);
        }

        // Normally the state would be a one-time use token, however in our
        // simple case, we want a user to be able to connect and disconnect
        // without reloading the page.  Thus, for demonstration, we don't
        // implement this best practice.
        //$app['session']->set('state', '');

        $code = $request->getContent();
        // Exchange the OAuth 2.0 authorization code for user credentials.
        $client->authenticate($code);
        $token = json_decode($client->getAccessToken());

        // You can read the Google user ID in the ID token.
        // "sub" represents the ID token subscriber which in our case
        // is the user ID. This sample does not use the user ID.
        $attributes = $client->verifyIdToken($token->id_token, CLIENT_ID)
            ->getAttributes();
        $gplus_id = $attributes["payload"]["sub"];

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

    return new Response($response, 200);
});

Now when I send the code using the above implementation, I get an 500 messages that says the below

Google_AuthException Error fetching OAuth2 access token, message: 'invalid_grant' in ../vendor/google/google-api-php-client/src/auth/Google_OAuth2.php line 115

at Google_OAuth2->authenticate(array('scope' => 'https://www.googleapis.com/auth/plus.login'), '{ "token":"xxxxxxxx"}') in ../vendor/google/google-api-php-client/src/Google_Client.php line 131

at Google_Client->authenticate('{ "token":"xxxxxxx"}') in ../signin.php line 99

at {closure}(object(Request))

at call_user_func_array(object(Closure), array(object(Request))) in ../vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php line 117

at HttpKernel->handleRaw(object(Request), '1') in ../vendor/symfony/http-kernel/Symfony/Component/HttpKernel/HttpKernel.php line 61

at HttpKernel->handle(object(Request), '1', true) in ../vendor/silex/silex/src/Silex/Application.php line 504

at Application->handle(object(Request)) in ../vendor/silex/silex/src/Silex/Application.php line 481

at Application->run() in ../signin.php line 139

Funny enough I have had to worked once where I did receive a 200, but I cannot recreate it.

So I know I have definitely got the implementation wrong, but I have no clue on how to send it and get my refresh token. I can't find anywhere on the web that explains this. Is someone able to help me please.

UPDATE 16 Jan 2014

Using https://www.googleapis.com/oauth2/v1/tokeninfo?access_token= I can see that the token being produced from getToken is valid and is indeed valid for 1 hour.

I can confirm the json formation is correct by changing the way I am inputting into the Post request and if I don't do it properly I get a total failure.

Now I am going deeper into the php and look at this section Google_OAuth2.php line 115 where it is breaking it is throwing a Google_AuthException. The code is below and this is provided in the quick starter pack

/**
 * @param $service
 * @param string|null $code
 * @throws Google_AuthException
 * @return string
 */
public function authenticate($service, $code = null) {
  if (!$code && isset($_GET['code'])) {
    $code = $_GET['code'];
  }

  if ($code) {
    // We got here from the redirect from a successful authorization grant, fetch the access token
    $request = Google_Client::$io->makeRequest(new Google_HttpRequest(self::OAUTH2_TOKEN_URI, 'POST', array(), array(
      'code' => $code,
      'grant_type' => 'authorization_code',
      'redirect_uri' => $this->redirectUri,
      'client_id' => $this->clientId,
      'client_secret' => $this->clientSecret
    )));

    if ($request->getResponseHttpCode() == 200) {
      $this->setAccessToken($request->getResponseBody());
      $this->token['created'] = time();
      return $this->getAccessToken();
    } else {
      $response = $request->getResponseBody();
      $decodedResponse = json_decode($response, true);
      if ($decodedResponse != null && $decodedResponse['error']) {
        $response = $decodedResponse['error'];
      }
      throw new Google_AuthException("Error fetching OAuth2 access token, message: '$response'", $request->getResponseHttpCode());
    }
  }

  $authUrl = $this->createAuthUrl($service['scope']);
  header('Location: ' . $authUrl);
  return true;
}

I edit the code above to make sure the code, the client id and secret were correct and they were. So that is where I am now, I don't think it is scope issues as well as I hard coded it in the client setup and still does not work. Not too sure.

UPDATE 23rd January

OK, I think it is a time issue. I used https://developers.google.com/+/photohunt/android and base my design on the BaseActivity in the Photohunt using the AuthUtil, and I get invalid grant on my server. How do I move the time back on my server in code. I read somewhere I can do time() - 10 somewhere but not sure where...

2

2 Answers

4
votes

It sounds like you may be sending the same authorization code multiple times. On Android GoogleAuthUtil.getToken() caches any tokens that it retrieves including authorization codes.

If you ask for a second code without invalidating the previous code, GoogleAuthUtil will return the same code. When you try to exchange a code on your server which has already been exchanged you get the invalid_grant error. My advice would be to invalidate the token immediately after you retrieve it (even if you fail to exchange the code, you are better off getting a new one than retrying with the old one).

code = GoogleAuthUtil.getToken(
    OneTimeCodeActivity.this,         // Context context
    mPlusClient.getAccountName(),     // String accountName
    scopes,                           // String scope
    appActivities                     // Bundle bundle
);

GoogleAuthUtil.invalidateToken(
    OneTimeCodeActivity.this,
    code
);

invalid_grant can be returned for other reasons, but my guess is that caching is causing your problem since you said it worked the first time.

0
votes

This issue is now resolved. This was due to the implementation on the One Time Code exchange with the server

As specified in the my issue above, I used the photohunt example to do the exchange with my server. The Android code can be found on the below link

https://github.com/googleplus/gplus-photohunt-client-android/blob/master/src/com/google/plus/samples/photohunt/auth/AuthUtil.java

One line 44 it reads this

byte[] postBody = String.format(ACCESS_TOKEN_JSON, sAccessToken).getBytes();

This will only work if on the server side you handle the JSON. I did not.

When calling up $client->authenticate($code); in php, $code had a JSON string and therefore when calling https://accounts.google.com/o/oauth2/token the authorization code was wrong.

So it was easy as I was not sending the code in the right format.

I found this out when digging and testing https://accounts.google.com/o/oauth2/token and created a manual cURL to test the token.

As provided in the Google+ API it was stated that all examples included a One Time Code exchange, but I think the code across all platform are not consistent and one has to double check themselve to make sure everything flows correctly, which was my mistake.