Gmail API via Service Accounts using PHP

Gmail API via Service Accounts

It seemed so simple (not really). All I wanted to do was access the Gmail API via Service accounts but this was a long and arduous quest, one that I at points believed my co-workers sent me on as a sort of initiation or snipe hunt (boy scouts should get that reference), if you will.

Anyway, I won’t bore you with all the wrong turns and miscues of it but will just tell you the goal and how to get there.

You see, I’m sub-contracting out to a web development firm and their client uses Google Apps for Education. They’ve got an LMS and when their students login, they want to be able to show the students how many unread gmails they have in their mailboxes. Furthermore, they may want the ability to display the “x” most recent unread messages too.

That said, originally I thought we could just use Oauth and have the very first time the student logged in, they would give the LMS application authorization to read their gmail accounts but that’s not what they wanted. Since this was Google Apps, and the accounts technically belong to the school, they didn’t want to confuse the issue with authorization, have to worry about the students not granting it or dealing with an extra login step. So that’s when I started to investigate using a Service Account.

A service account, according to Google is, “an account that belongs to your application instead of to an individual end user. Your application calls Google APIs on behalf of the service account, so users aren’t directly involved.” That’s what we wanted, to access Gmail API via Service Account. Here’s the principle reference I found from Google on the subject: Using OAuth 2.0 for Server to Server Applications. There are some step by step instructions and I’ll go through them in a moment. But the first thing you need to do is download the client library. I’m working in PHP, so that’s the library I downloaded.

Download the Client Library from Github

You can find the info for the PHP Client library here: https://developers.google.com/api-client-library/php/. If you’ve never done anything with Google APIs and the client library, check out the Get Started area: https://developers.google.com/api-client-library/php/start/get_started but you can find the client library on Github here: https://github.com/google/google-api-php-client. Here are the releases: https://github.com/google/google-api-php-client/releases. I did this with Version 2.0 but I see now that there’s a version 2.02 out there. Whichever you use, download the all the files to your root directory of your application (or if somewhere else, note it because you will need to include them in your program).

Create a Service Account

Go to your developers console in Google (https://console.developers.google.com). If you haven’t already done so you will need to create a project, otherwise just select an existing project (Note: I created the service account logged in as a user who had admin access to the Google Apps account).

Follow the instructions on the Using OAuth 2.0 for Server to Server Applications:

Open the Service accounts page. If prompted, select a project.

Click Create service account.

Gmail API via Service Accounts

You will find Service Account under IAM & Admin in the left sidebar

In the Create service account window, type a name for the service account, and select Furnish a new private key. If you want to grant Google Apps domain-wide authority to the service account (and you should for the purposes of this example), also select Enable Google Apps Domain-wide Delegation. Then click Create.

Gmail API via Service Accounts

Your new public/private key pair is generated and downloaded to your machine; it serves as the only copy of this key. You are responsible for storing it securely.

Note that last line about the public/private key pair. I believe that is out of date. They still support this methodology but are now recommending you use the json file and download that. Remember it’s location and it should be stored somewhere that will not be accessible on the web to prying eyes.

For the key generated, I used the JSON file that I downloaded to my local machine, noting it’s location (I put mine in my root folder for my application for this purpose).

Domain Wide Authority

Remember to delegate domain-wide authority when creating the service account as stated:

To delegate domain-wide authority to a service account, first enable domain-wide delegation for an existing service account in the Service accounts page or create a new service account with domain-wide delegation enabled.
Then, an administrator of the Google Apps domain must complete the following steps:

  1. Go to your Google Apps domain’s Admin console.
  2. Select Security from the list of controls. If you don’t see Security listed, select More controls from the gray bar at the bottom of the page, then select Security from the list of controls. If you can’t see the controls, make sure you’re signed in as an administrator for the domain.
  3. Select Show more and then Advanced settings from the list of options.
  4. Select Manage API client access in the Authentication section.
  5. In the Client Name field enter the service account’s Client ID. You can find your service account’s client ID in the Service accounts page.
  6. In the One or More API Scopes field enter the list of scopes that your application should be granted access to. For example, if your application needs domain-wide access to list Gmail (no updates), enter: https://www.googleapis.com/auth/gmail.readonly
  7. Click Authorize.

Now if you want to jump ahead a bit, feel free to page down to the section on the “missing step” as you’re going to want to do that from the Developer’s Console, but if you want to  see how it played out for me, just keep reading in this sequence.

The PHP Code

Next, I cobbled together this code, from various sources:


 

require_once (‘googleapi2/google/vendor/autoload.php’);  //This is from the PHP Google library, get that from github and include the library on your machine (in this case it’s local on my PC). I put it in my root directory of my application
$client = new Google_Client();
$client->setScopes([“https://www.googleapis.com/auth/gmail.readonly”]);
$client->setApplicationName(“MyProject”); // I don’t think this matters
//************************************************************************************************************************************
// Either this code
$user_to_impersonate = ‘the email account you want to access’; //very important
$client->setSubject($user_to_impersonate); //important
//***********************************************************************************************************************************
// Or this code – you don’t need both
$client->setConfig(‘subject’,$user_to_impersonate); //either the above method, or this will work
//************************************************************************************************************************************
$client->setAuthConfig(‘downloadedjson-file-name.json’); //this is the json file you downloaded earlier
$client->setAccessType(‘offline’); //not sure this is necessary for this type of call

$gmailService = new Google_Service_Gmail($client);

$googlemessage=listMessages($gmailService, ‘me’); //using ‘me’ means using the subject set above

function listMessages($service, $userId) {
$pageToken = NULL;
$messages = array();
$opt_param = array();
$success = true;
$opt_param[‘q’]=”is:unread”;  //this will get us only unread messages
$opt_param[‘maxResults’]=2; //this limits the amount of unread messages returned

try {
$messagesResponse = $service->users_messages->listUsersMessages($userId, $opt_param);
if ($messagesResponse->getMessages()) {
$messages = array_merge($messages, $messagesResponse->getMessages());
}
} catch (Exception $e) {
echo “<h2>you don’t have email access</h2>”;
echo “<br />”;
echo $e;
$success=false;
}

if ($success) {
foreach ($messages as $message) {
print ‘Message with ID: ‘ . $message->getId() . ‘<br/>’;
$messageText = getMessage($service,$userId,$message->getId());
print $messageText[‘snippet’];
}
echo “<br />Number of Unread Messages is “. $messagesResponse[‘resultSizeEstimate’] .”<br />”;
}
}

/**
* Get Message with given ID.
*
* @param  Google_Service_Gmail $service Authorized Gmail API instance.
* @param  string $userId User’s email address. The special value ‘me’
* can be used to indicate the authenticated user.
* @param  string $messageId ID of Message to get.
* @return Google_Service_Gmail_Message Message retrieved.
*/

function getMessage($service, $userId, $messageId) {

try {

$message = $service->users_messages->get($userId, $messageId);
print ‘Message with ID: ‘ . $message->getId() . ‘ retrieved.’;
return $message;
} catch (Exception $e) {
print ‘An error occurred: ‘ . $e->getMessage();
}
}


 

The code will get 2 unread messages, display their message ids, get their detailed content and print a snippet as well as display the estimate of the number of unread messages that the user has (it’s an estimate and isn’t 100% accurate).

Anyway, when executing that code from my local machine (i.e., my desktop), I crashed and burned with this error:

‘GuzzleHttp\Exception\RequestException’ with message ‘cURL error 60: SSL certificate problem: unable to get local issuer certificate (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)’ in C:\wamp\www\email\googleapi2\google\vendor\guzzlehttp\guzzle\src\Handler\CurlFactory.php:187 Stack trace: #0

Guzzle Error

Now here’s the thing, a while ago, I found an post about this (https://laracasts.com/discuss/channels/general-discussion/curl-error-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate/replies/37017) so I downloaded a new cacert.pem file and updated my php.ini file as instructed but that didn’t correct my situation so I pretty much set it aside for a while.

Then I got put in touch with someone at Google who said they would try to look into the problem though they don’t normally support the API stuff. I sent that person a ton of information, my code, the error and some array dumps and patiently waited for a reply for 24 hours.

Okay, I admit, I’m not that patient.

So I started to poke around again. I tried a few different things but nothing. So once again I did a search on that error and this time, I found this post first: https://laracasts.com/discuss/channels/general-discussion/curl-error-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate?page=2 and in it I read this, “But you should get a fresh copy of the cert file as GeoffGordon stated” which led me to this link: https://laracasts.com/discuss/channels/general-discussion/curl-error-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate/replies/52954 where I read this, “The correct answer is indeed to put the cacert.pem file and amend the php.ini file to match as suggested by Moez above. ….. but I kept on getting CURL error 60’s The trick was getting a clean copy of the PEM file! Any method involving windows notepad or other editors corrupts the file and gives the cURL error 60 etc.” Eureka!

That led me to this download: https://gist.github.com/VersatilityWerks/5719158/download and a clean copy of the cacert.pem file!

So you think that would fix everything?

The Missing Step

Not exactly but it got me through to a Google error which I unfortunately did not copy but it was basically telling me I never enabled that API! So you see there was a step missing from the Google instructions and that is this:

After you create your service account, go back to the API Manager in the Developers Console, click Library, click on the Gmail API under Google Apps API and enable it. Voila! It worked!!

Comments are closed.