phpgmail-api

How do I make my php app never ask for the Gmail API verification code?


I would like the app to work automatically, but while we're testing, if we spend the weekend without messing with it on Monday, the first thing the app does is:

Open the following link in your browser: https://accounts.google.com/o/oauth2/(...) Enter verification code:

Somewhere over the web I had seen once that it was necessary to bold the options in the code below; but not working as expected.

use GuzzleHttp\Client;
require __DIR__ . '/vendor/autoload.php';
if (php_sapi_name() != 'cli') {
    throw new Exception('This application must be run on the command line.');
}

// @return Google_Client the authorized client object
function getClient()
{
    $guzzleClient = new GuzzleHttp\Client([
                'proxy' => '<my_ip_proxy_pfsense>:<port>',
                'verify' => false,
        ]);

$client = new Google_Client();
$client->setApplicationName('RS Gmail Check API PHP');
$client->setScopes(Google_Service_Gmail::GMAIL_READONLY);
$client->setAuthConfig('credentials.json');
**$client->setAccessType('offline');**
// Using "consent" ensures that your application always receives a refresh token.
// If you are not using offline access, you can omit this.
**$client->setApprovalPrompt("consent");
$client->setIncludeGrantedScopes(true);   // incremental auth**
//
$client->setPrompt('select_account consent');
$client->setHttpClient($guzzleClient);

$tokenPath = '/opt/gmail-check/token.json';
    if (file_exists($tokenPath)) {
        $accessToken = json_decode(file_get_contents($tokenPath), true);
        $client->setAccessToken($accessToken);
    } else {
        $authUrl = $client->createAuthUrl();
        header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL));
        if (isset($_GET['code'])) {
            $authCode = $_GET['code'];
            // Exchange authorization code for an access token.
            $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
            header('Location: ' . filter_var($this->redirectUri, 
            FILTER_SANITIZE_URL));

if(!file_exists(dirname($this->tokenFile))) {
                mkdir(dirname($this->tokenFile), 0700, true);
            }

            file_put_contents($this->tokenFile, json_encode($accessToken));
        }else{
            exit('No code found');
        }
        }
    $client->setAccessToken($accessToken);
// If there is no previous token or it's expired.
    if ($client->isAccessTokenExpired()) {
        // Refresh the token if possible, else fetch a new one.
        if ($client->getRefreshToken()) {
            $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
        } else {
$authUrl = $client->createAuthUrl();
            printf("Open the following link in your browser:\n%s\n", $authUrl);
            print 'Enter verification code: ';
            $authCode = trim(fgets(STDIN));

            // Exchange authorization code for an access token.
            $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
            $client->setAccessToken($accessToken);

            // Check to see if there was an error.
            if (array_key_exists('error', $accessToken)) {
                throw new Exception(join(', ', $accessToken));
            } // */
        }
        // Save the token to a file.
        if (!file_exists(dirname($tokenPath))) {
            mkdir(dirname($tokenPath), 0700, true);
        }
        file_put_contents($tokenPath, json_encode($client->getAccessToken()));
    }
    return $client;
}

// Get the API client and construct the service object.
$client = getClient();
$service = new Google_Service_Gmail($client);

// Print the labels in the user's account.
$user = 'me';
$inboxMessage = [];

function decodeBody($body) {
    $rawData = $body;
    $sanitizedData = strtr($rawData,'-_', '+/');
    $decodedMessage = base64_decode($sanitizedData);
    if(!$decodedMessage){
        $decodedMessage = FALSE;
    }
    return $decodedMessage;
}
$list = $service->users_messages->listUsersMessages($user, ['maxResults' => 10, 'q' => $search]);
try{
        foreach ($list->getMessages() as $mlist) {

            $message_id = $mlist->id;
            $optParamsGet2['format'] = 'full';
            $single_message = $service->users_messages->get('me', $message_id, $optParamsGet2);
            $payload = $single_message->getPayload();
            $headers = $single_message->getPayload()->getHeaders();
            $snippet = $single_message->getSnippet();

            foreach($headers as $single) {

            if ($single->getName() == 'Subject') {

                $message_subject = $single->getValue();

            }
 else if ($single->getName() == 'Date') {

                $message_date = $single->getValue();
                $message_date = date('M jS Y h:i A', strtotime($message_date));
            }

            else if ($single->getName() == 'From') {

                $message_sender = $single->getValue();
                $message_sender = str_replace('"', '', $message_sender);
            }
        }

            $inboxMessage = [
                'messageId' => $message_id,
                'messageSnippet' => $snippet,
                'messageSubject' => $message_subject,
                'messageDate' => $message_date,
                'messageSender' => $message_sender
                ];

            // With no attachment, the payload might be directly in the body, encoded.
            $body = $payload->getBody();
            $FOUND_BODY = decodeBody($body['data']);

            // If we didn't find a body, let's look for the parts
            if(!$FOUND_BODY) {
                $parts = $payload->getParts();
                foreach ($parts  as $part) {
                    if($part['body']) {
                        $FOUND_BODY = decodeBody($part['body']->data);
                    }
                    // Last try: if we didn't find the body in the first parts,
                    // let's loop into the parts of the parts (as @Tholle suggested).
                    if($part['parts'] && !$FOUND_BODY) {


foreach ($part['parts'] as $p) {
                            // replace 'text/html' by 'text/plain' if you prefer
                            if($p['mimeType'] === 'text/html' && $p['body']) {
                                $FOUND_BODY = decodeBody($p['body']->data);
                            }
                        }
                    }
                }
            }

Update: I spent this weekend doing some testing with another project using the Gmail API but based on the same code I am using in the service, and even though I spent more than 5 hours without running the application, no time was asked for the token more than once. . The most significant difference is that in the service GuzzleClient is configured to work with the proxy (which would be our pfsense firewall). Am I experiencing this token issue expiring at work because of the proxy?


Solution

  • How to incorporate the refresh token into your APP

    Follow the Google API quickstart for PHP. Sample:

    <?php
    require __DIR__ . '/vendor/autoload.php';
    
    if (php_sapi_name() != 'cli') {
        throw new Exception('This application must be run on the command line.');
    }
    
    /**
     * Returns an authorized API client.
     * @return Google_Client the authorized client object
     */
    function getClient()
    {
        $client = new Google_Client();
        $client->setApplicationName('Gmail API PHP Quickstart');
        $client->setScopes(Google_Service_Gmail::GMAIL_READONLY);
        $client->setAuthConfig('credentials.json');
        $client->setAccessType('offline');
        $client->setPrompt('select_account consent');
    
        // Load previously authorized token from a file, if it exists.
        // The file token.json stores the user's access and refresh tokens, and is
        // created automatically when the authorization flow completes for the first
        // time.
        $tokenPath = 'token.json';
        if (file_exists($tokenPath)) {
            $accessToken = json_decode(file_get_contents($tokenPath), true);
            $client->setAccessToken($accessToken);
        }
    
        // If there is no previous token or it's expired.
        if ($client->isAccessTokenExpired()) {
            // Refresh the token if possible, else fetch a new one.
            if ($client->getRefreshToken()) {
                $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
            } else {
                // Request authorization from the user.
                $authUrl = $client->createAuthUrl();
                printf("Open the following link in your browser:\n%s\n", $authUrl);
                print 'Enter verification code: ';
                $authCode = trim(fgets(STDIN));
    
                // Exchange authorization code for an access token.
                $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
                $client->setAccessToken($accessToken);
    
                // Check to see if there was an error.
                if (array_key_exists('error', $accessToken)) {
                    throw new Exception(join(', ', $accessToken));
                }
            }
            // Save the token to a file.
            if (!file_exists(dirname($tokenPath))) {
                mkdir(dirname($tokenPath), 0700, true);
            }
            file_put_contents($tokenPath, json_encode($client->getAccessToken()));
        }
        return $client;
    }
    
    
    // Get the API client and construct the service object.
    $client = getClient();
    $service = new Google_Service_Gmail($client);
    
    // Print the labels in the user's account.
    $user = 'me';
    $results = $service->users_labels->listUsersLabels($user);
    
    if (count($results->getLabels()) == 0) {
      print "No labels found.\n";
    } else {
      print "Labels:\n";
      foreach ($results->getLabels() as $label) {
        printf("- %s\n", $label->getName());
      }
    }
    

    UPDATE:

    After you provided your complete code, I can see that in addition to the required authorization flow, you have the following spare lines:

    else {
            $authUrl = $client->createAuthUrl();
            header('Location: ' . filter_var($authUrl, FILTER_SANITIZE_URL));
            if (isset($_GET['code'])) {
                $authCode = $_GET['code'];
                // Exchange authorization code for an access token.
                $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
                header('Location: ' . filter_var($this->redirectUri, 
                FILTER_SANITIZE_URL));
    
    if(!file_exists(dirname($this->tokenFile))) {
                    mkdir(dirname($this->tokenFile), 0700, true);
                }
    
                file_put_contents($this->tokenFile, json_encode($accessToken));
            }else{
                exit('No code found');
            }
            }
    

    So, e.g. the lines

    $authUrl = $client->createAuthUrl();
    printf("Open the following link in your browser:\n%s\n", $authUrl);
    

    create an authorization URL and prompt for authentication, however mind that in the

    if ($client->isAccessTokenExpired()) {
            // Refresh the token if possible, else fetch a new one.
    

    you are doing the same again - this would explain why you are prompted for authentication for often than expected.

    Also, you have the lines

    $client->setAccessToken($accessToken);
    

    twice.

    If you remove the spare code and erase your token file (to trigger new authentication flow), your problem should be solved.