Search code examples
google-drive-apigoogle-oauth

Google Drive API - Service Account : make a request for access token


I am a newbie in Google API. I start my project with Google Drive API and I follow the tutorial in this google cloud document.

On the Google Cloud Console - I create my project, enable the "Google Drive API". After that, I create a "Service account" and its private key, and then save the "JSON private key file" to my local server. At this process, I didn't create any "oAuth 2.0 Client IDs" or "API Keys".

This is my "JSON private key file" in my local server:

{
  "type": "service_account",
  "project_id": "coastal-scanner-338317",
  "private_key_id": "a093062296b1cbdecab6133f6b91e470bfbca0e4",
  "private_key": "-----BEGIN PRIVATE KEY-----\n*[My Private Key]*\n-----END PRIVATE KEY-----\n",
  "client_email": *[My Client Email]*,
  "client_id": *[My Client ID]*,
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/*[My Client Email]*"
}

and this is my PHP file for request an "Access Token" from google.

    //Create Service Account's JWT
    $serviceFile = './mykey.json';  //My Private key file
    $scope='https://www.googleapis.com/auth/drive';  
    
    // Get Data from Google Service Account Oauth2 json file
    $service = json_decode(file_get_contents($serviceFile));
    
    $header = '{"alg":"RS256","typ":"JWT"}';
$aud = $service->auth_uri; // 'https://oauth2.googleapis.com/token'
    $iat = time(); $exp =  $iat + 3600;
    $claimset = '{"iss":"' . $service->client_email . '","scope":"' . $scope . '","aud":"' . $aud . '","exp":' . $exp . ',"iat":' . $iat . '}';

//URL-safe Base64 header and claimset
    $hc = base64url_encode(mb_convert_encoding($header,'UTF-8')) . '.' . base64url_encode(mb_convert_encoding($claimset,'UTF-8'));
    
// Signature with Sha256withRSA follow Google spec = RS256
    $bytepkey = openssl_pkey_get_private($service->private_key);

    openssl_sign($hc,$signature,$bytepkey,'sha256');
    openssl_free_key($bytepkey);

    $assertion =  $hc . '.' . base64url_encode($signature);
    $grant_type = urlencode('urn:ietf:params:oauth:grant-type:jwt-bearer');
    $param = "grant_type={$grant_type}&assertion={$assertion}";

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $service->auth_uri);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $user_agent);
    curl_setopt($ch, CURLOPT_POST , 1);
    curl_setopt($ch, CURLOPT_POSTFIELDS , $param);
    $result = curl_exec($ch);
    echo $result;

Above code contain 2 function for url-safe base64 like this:

function base64url_encode($data) {
  return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
function base64url_decode($data) {
  return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
}

I open the php file on my browser and see the result like this:

ฺResult on my Chrome Browser

I click on the link in the picture and it bring me to a user consent page.

Does it mean my HTTP request for access token failed or else? What're the mistakes I did?


Solution

  • I see that you are trying to do this manually and i would like to recomend that you not. Creating the jws to authorize a service account is tricky. Its much easer to just use the Google php client library

    My sample ServiceAccount.php

    require_once __DIR__ . '/vendor/autoload.php';
    
    // Use the developers console and download your service account
    // credentials in JSON format. Place the file in this directory or
    // change the key file location if necessary.
    putenv('GOOGLE_APPLICATION_CREDENTIALS='.__DIR__.'/service-account.json');
    
    /**
     * Gets the Google client refreshing auth if needed.
     * Documentation: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
     * Initializes a client object.
     * @return A google client object.
     */
    function getGoogleClient() {
        return getServiceAccountClient();
    }
    
    
    function getServiceAccountClient() {
        try {   
            // Create and configure a new client object.        
            $client = new Google_Client();
            $client->useApplicationDefaultCredentials();
            $client->addScope([YOUR SCOPES HERE]);
            return $client;
        } catch (Exception $e) {
            print "An error occurred: " . $e->getMessage();
        }
    }
    

    Really let the client library do all the heavy lifting. The Google analytics api has a nice service account example for php. Just take the authorization stuff and replace google analytics with drive. Let me know if you have any issues Google analytics quickstart