Search code examples
phpgoogle-apigoogle-drive-apigoogle-oauthgoogle-api-php-client

How do you use Google Drive API to permanently authorize an user account to upload files to your Drive?


I am trying to get my web application to use a dedicated email account to upload files to google drive through the API. The problem being that it keeps requiring the potential users to authenticate the account that I prepared for it, which is exactly what I am trying to prevent the need for.

Any solutions I came across directed me at using the refresh token to ensure the account stays authorized. This still comes with the issue that on the first time use, an user has to authenticate said account. Saving the access token with the refresh token locally didn't work, as it still forced new users to authenticate. the snippet below displays the code used to authenticate.

class AccessDrive
{
    private $client;

    //initialize the client data provided on the call of the function
    private function initClient($scopes, $clientsecret, $returnUrl)
    {
        $client = new Google_Client();
        try{$client->setAuthConfig($clientsecret);}catch(Google_Exception $e){echo $e;}
        $client->setClientId(CLIENT_ID);
        $client->setApplicationName(APPLICATION_NAME);
        $client->setAccessType('offline');
        //$client->setApprovalPrompt('force');
        $client->setIncludeGrantedScopes(true);   // incremental auth
        foreach ($scopes as $scope) { 
            $client->addScope($this->getScope($scope)); //assume one or multiple from the google drive scopes
        }
        $client->setRedirectUri( $returnUrl);
        return $client;
    }

    public function Login()
    {
        if (!$_SESSION['code']) {
            $auth_url = $this->client->createAuthUrl();
            header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
        } else {
            error_reporting(-1 & ~E_WARNING);
            $this->client->authenticate($_SESSION['code']);
        }
        return false;
    } 

    public function __construct($returnUrl, $scopes, $clientSecret)
    {
        $this->client = $this->initClient($scopes, $clientSecret, $returnUrl);
        $this->Login();
    }
}
?>

How does one make sure that the application will not need to prompt users to authenticate, staying authenticated outside the functionality the user is supposed to access?

--> Edit: After attempting to implement the suggested method, by DaImTo, the following code generates a Google_Service_Exception, stating that the client is unauthorized to retrieve access tokens with this method. I am not sure where that goes wrong.

class AccessDrive
{
    private $client;

    public function __construct( $scopes, $credentials, $email, $toImpersonate )
    {
        putenv( "GOOGLE_APPLICATION_CREDENTIALS=".$credentials);
        $scopelist =[];
        foreach ($scopes as $scope) {
            array_push($scopelist, $this->getScope($scope));
        }
        $client = new Google_Client();
        $client->useApplicationDefaultCredentials();
        $client->addScope($scopelist);
        $client->setSubject($toImpersonate);
        $this->client = $client;
    }

--> Edit: If you downvote, I would like to know why so I can improve my current and future questions.


Solution

  • You are currently authencating using Oauth2. Oauth2 allows you to request access from a user to access their google drive account. Assuming they grant you access you are given an access token which allows you to access their data for an hour. If you request offline access you will be given a refresh token which you can use to request a new access token when ever the access token expires.

    In your case becouse you are trying to access your own drive account always i sugest you look into using a service account instead. Service accounts are pre approved you share a folder on your google drive account with it and it will have access to that drive account as long as you dont remove the permissions. I have a post about how service accounts work Google developer for beginners service account

    Example:

    // Load the Google API PHP Client Library.
    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();
    }
    /**
     * Builds the Google client object.
     * Documentation: https://developers.google.com/api-client-library/php/auth/service-accounts
     * Scopes will need to be changed depending upon the API's being accessed. 
     * array(Google_Service_Analytics::ANALYTICS_READONLY, Google_Service_Analytics::ANALYTICS)
     * List of Google Scopes: https://developers.google.com/identity/protocols/googlescopes
     * @return A google client object.
     */
    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();
        }
    }
    

    code ripped form my sample project serviceaccount.php

    Usage:

    require_once __DIR__ . '/vendor/autoload.php';
    session_start();
    require_once __DIR__ . '/ServiceAccount.php';     
    $client = getGoogleClient();
    $service = new Google_Service_Drive($client); 
    

    All your requests then get sent using $service