1

I am creating an API request (GET bucket) for the storage API and one of the required parameter is the "Authorization" header.

Please note that I am using a Service Account to access the API.

I followed the document https://developers.google.com/accounts/docs/OAuth2ServiceAccount to get the access token for the "Authorization" header so I could send an authorized request to their REST API. The problem is I am always getting the "invalid_grant" error.

Use this code to check it out:

<?php
error_reporting(E_ERROR);

const CLIENT_ID = 'XXXXXXXXXXXX.apps.googleusercontent.com';
const SERVICE_ACCOUNT = 'XXXXXXXXXXXX@developer.gserviceaccount.com';
const KEY_FILE = 'XXX.p12';

function get_oauth_access_token()
{
    $header[alg] = 'RS256';
    $header[typ] = 'JWT';

    $header = urlencode(base64_encode(utf8_encode(json_encode($header))));

    $assertion_time = time();

    $claim[iss] = CLIENT_ID; //also tried SERVICE_ACCOUNT here, no improvement
    $claim[scope] = 'https://www.googleapis.com/auth/devstorage.read_only';
    $claim[aud] = 'https://accounts.google.com/o/oauth2/token';
    $claim[exp] = $assertion_time + 3600;
    $claim[iat] = $assertion_time;

    $claim = urlencode(base64_encode(utf8_encode(json_encode($claim))));

    $data = $header . '.' . $claim;

    $p12 = file_get_contents(KEY_FILE);
    $cert = array();
    openssl_pkcs12_read($p12, $cert, 'notasecret');
    $priv_key_id = openssl_get_privatekey($cert[pkey]);

    openssl_sign($data, $signature, $priv_key_id, 'sha256');

    $signature = urlencode(base64_encode($signature));

    $assertion = $data . '.' . $signature;

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://accounts.google.com/o/oauth2/token');
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, array('grant_type'=>'assertion', 
                                               'assertion_type'=>'http://oauth.net/grant_type/jwt/1.0/bearer', 
                                               'assertion'=>$assertion));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);


    $result = curl_exec($ch);
    $error = curl_error($ch);

    curl_close($ch);
    var_dump($result);
    var_dump($error);
}

get_oauth_access_token();

Is there anything wrong in this code that causes the "invalid_grant" error?

Jean-Rémy Revy
  • 5,607
  • 3
  • 39
  • 65
JP _
  • 530
  • 4
  • 14

4 Answers4

2

Your problem might be the time of your server.

I faced a problem with Google Analytics: all my code was correct but the time of my server was some seconds in the future compared with Google's time. You can delay your server's time a few minutes to do a test.

If it works you can use NTP for example, to keep the server clock correct.

bool.dev
  • 17,508
  • 5
  • 69
  • 93
0

Here's a simple PHP program illustrating use of a service account with the Google Cloud Storage RESTful HTTP interface. I've tested this code with a service account and it seems to work fine. Let me know if you have any further questions.

<?php

require_once 'apiClient.php';

// Define constants.
const CLIENT_ID = 'YOUR_CLIENT_ID_GOES_HERE';
const SERVICE_ACCOUNT_NAME = 'YOUR_SERVICE_ACCOUNT_NAME_GOES_HERE';
const KEY_FILE = 'key.p12';
const BUCKET = 'marc-us';

// Obtain OAuth 2.0 access token for service account.
$client = new apiClient();
$key = file_get_contents(KEY_FILE);
$client->setAssertionCredentials(new apiAssertionCredentials(
  SERVICE_ACCOUNT_NAME,
  array('https://www.googleapis.com/auth/devstorage.full_control'),
  $key)
);
$client::$auth->refreshTokenWithAssertion();
$json = $client->getAccessToken();
$accessToken = json_decode($json)->access_token;

// Add access token to Authorization header of HTTP request.
$ch = curl_init('http://commondatastorage.googleapis.com/' . BUCKET);
curl_setopt($ch, CURLOPT_HTTPHEADER, 
            array('Authorization: OAuth ' . $accessToken));
$resp = curl_exec($ch);
curl_close($ch);

// Display results.
print '<h2>Response:</h2><pre>' . $resp . '</pre>';
?>
Marc Cohen
  • 3,742
  • 2
  • 19
  • 19
  • Nope, it doesn't work as I've stated here: https://groups.google.com/forum/?fromgroups#!topic/gs-discussion/3y_2XVE2q7U The code you've suggested is probably just a workaround and adds bloat, which requires the SDK just to get the access token. – JP _ Jun 15 '12 at 17:30
0

The code in my last response works fine for me so I suspect you're facing an environmental problem. At any rate, I consulted with the owner of the Google PHP client library and he provided a better way to refresh the access token without resorting to calls to the internal refreshTokenWithAssertion() method. He suggested this technique:

  $req = new apiHttpRequest(YOUR_URL);
  $val = $client->getIo()->authenticatedRequest($req);

The call to authenticatedRequest() takes care of refreshing the access token (with assertion credentials if they're set) as needed. I modified the code above to use this approach and it works fine for me. Note that both the old and new versions work for me so there's no functional difference but I think the new version is better because it avoids the internal call, uses the Google PHP client lib instead of curl to execute the request and results in much shorter, simpler code.

<?php

require_once 'apiClient.php';

// Define constants.
const SERVICE_ACCOUNT_NAME = 'YOUR_SERVICE_ACCOUNT_NAME';
const KEY_FILE = 'key.p12';
const BUCKET = 'marc-us';

// Obtain service account credentials assertion.
$client = new apiClient();
$key = file_get_contents(KEY_FILE);
$client->setAssertionCredentials(new apiAssertionCredentials(
  SERVICE_ACCOUNT_NAME,
  array('https://www.googleapis.com/auth/devstorage.full_control'),
  $key)
);

// Create HTTP request, authorize and execute.
$req = new apiHttpRequest('http://commondatastorage.googleapis.com/' . BUCKET);
$resp = $client::getIo()->authenticatedRequest($req);

// Display results.
print '<h2>Response:</h2>' . $resp->getResponseBody();

?>
Marc Cohen
  • 3,742
  • 2
  • 19
  • 19
0

I had the same error, but with Java. My problem was that the time in seconds to exp and iat was in GMT-5 and the API google Auth is in GMT. Then I changed the time to GMT value and all is OK.

ndaniel8a
  • 96
  • 5