The following code https://stackoverflow.com/a/3649148 works all the while, until it broken recently when Google has changed their security policy.
I received mail from Google
Hi xxx, Someone just tried to sign in to your Google Account [email protected] from an app that doesn't meet modern security standards.
We strongly recommend that you use a secure app, like Gmail, to access your account. All apps made by Google meet these security standards. Using a less secure app, on the other hand, could leave your account vulnerable. Learn more.
Google stopped this sign-in attempt, but you should review your recently used devices:
I look at https://support.google.com/accounts/answer/6010255?hl=en-GB
I was wondering, what is the way to implement a correct sign-in attempt, in order to continue to send email via Gmail SMTP, with 0 configuration from user side?
I had to implement this on a PHP-based webpage because our server does not have a mail server and we are using google's one instead. It is likely Google will cut any unauthorized access to their services in the future; and we want to have a future-proof solution. I believe porting this solution to other languages (such as tagged Java) should be no big issue.
Google mail account with Cloud Console enabled, a web domain with https enabled, PHP 5.4 or greater with JSON extension (bundled since v5.2, but sometimes not installed anyway - we presume it is already installed) and a lot of patience.
Additionally, we need PHP Google API Client library, that can be obtained using:
OR
First, you need to create a project in google console. Perform this setup on an account you want to associate with your webpage mail-sending application - YES, this is going to be an application - from Google's point of view. Once logged in: in the header menu in project selection, select and create a new project (mine is called Gmail API from now on). I will be also using a (non-existing) webpage called gmapi.xy.
Once a new project is created, go to the Library section, find Gmail API and enable it.
You obviously need some authentication data. The enablement of API should redirect you to Gmail API console interface - select Credentials in the left menu:
Create new OAuth client ID
You need to fill in name, support email, scope, authorized domains and OAuth limits
Scope : You need to specify all privileges granted to anyone who will authenticate to the Gmail API:
Authorized domains : Fill in the domain you want to send the emails from:
gmapi.xy
)https://gmapi.xy
OR https://gmapi.xy/policy
etc.)OAuth grant limits : I am satisfied with default setup so no changes (for more information on this topic see https://developers.google.com/analytics/devguides/config/mgmt/v3/limits-quotas)
Save changes : Save the form. We've asked for additional scopes - likely we will see a warning:
This app isn't verified.
The OAuth consent screen that is presented to the user may show the warning "This app isn't verified" if it is requesting scopes that provide access to sensitive user data. These applications must eventually go through the verification process to remove that warning and other limitations. During the development phase you can continue past this warning by clicking Advanced > Go to {Project Name} (unsafe).
Once your app is running, alive, public and not in the development phase, submit the application for verification (button next to Save
). The verification of your new app might take up to several weeks.
Now we are able to create the credentials - as before, select Credentials in the left menu. Create new OAuth client ID.
https://gmapi.xy
and https://www.gmapi.xy
test.php
(having https://www.gmapi.xy/test.php
URI) that is being redirected to itself. Thus, i will add there https://www.gmapi.xy/test.php
and https://gmapi.xy/test.php
.There are many problems with allowed redirect URLs, you must not end with
/
sign if your real URI does not have one, you must specify PORT number if you use other than default one... see this thread for more information. There is a checklist for you:
- http or https?
- & or &?
- trailing slash(/) or open ?
- (CMD/CTRL)+F, search for the exact match in the credential page. If not found then search for the missing one.
- Wait until google refresh it. May happen in each half an hour if you are changing frequently or it may stay in pool. For my case it was almost half an hour to take effect.
- Did you re-import the
credentials.json
file upon changing the values in your OAuth ID?NOTE: having multiple allowed URIs can harm your script - after successful authorization, you can still get this error after being redirected back to your webpage.
JSON
credentials file - in OAuth 2.0 Client IDs section, click the arrow iconcomposer require google/apiclient:^2.0
Google_Task_Composer::cleanup
task and specify the services you want to keep in composer.json
: {
"require": {
"google/apiclient": "^2.7"
},
"scripts": {
"post-update-cmd": "Google_Task_Composer::cleanup"
},
"extra": {
"google/apiclient-services": [
"Drive",
"YouTube"
]
}
}
NOTE: there seems to be issues with this approach that has not been resolved yet. Therefore I did not try this feature. I also could not find available services name list, so it would be good if someone found it and added a link.
Download any .zip
- a stable version from releases of your desired PHP version and place it somewhere in the website (upload the .zip
and extract it there).
Upload also the JSON
credentials file.
SECURITY NOTICE: place it somewhere safe: either remove the R/W privileges for anyone except owner or protect this file using
.htaccess
!
Then simply include autoload.php
(locate the library on the server for its path, it should be in vendor
folder) and provide the credentials for authentication.
require_once '/path/to/google-api-php-client/vendor/autoload.php';
$client = new Google_Client();
$client->setAuthConfig('/path/to/client_credentials.json');
Furthermore, you have to ask for the scope privileges and obtain a token
from the service. The available scope list is available here. Also, see this thread for more examples on scopes.
$client->setPrompt("consent");
$client->setScopes(array(
'https://www.googleapis.com/auth/gmail.send'
//add more if you want to have them, or add
// "https://mail.google.com/" to read, compose, send, delete mails
));
$client->setAccessType('offline');
$client->setIncludeGrantedScopes(true);
$tokenPath = 'where/you/want/to/store/token.json';
// Get new token - see redirect below
if (isset($_GET['code'])) {
$accessToken = $client->fetchAccessTokenWithAuthCode($_GET['code']);
// Save the token to a file.
if (!file_exists(dirname($tokenPath))) {
mkdir(dirname($tokenPath), 0700, true);
}
file_put_contents($tokenPath, json_encode($accessToken));
$client->setAccessToken($accessToken);
} else if (file_exists($tokenPath)) {
// Get the saved token
$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.
$refreshToken = $client->getRefreshToken();
if ($refreshToken) {
$client->fetchAccessTokenWithRefreshToken($refreshToken);
} else {
// Get the token - redirect to the same page
$redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
$client->setRedirectUri($redirect_uri);
$auth_url = $client->createAuthUrl();
// Actually GO to the authentication (and authorization) url
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
}
}
At first, the webpage redirects us to google where we need to grant access for our application to the desired email to send messages with. Once approved, a token file is created that we store in a file. When the token expires, a new token should be created automatically with fetchAccessTokenWithRefreshToken()
- more here.
But just in case, I would advise something like:
// Get the token - redirect to the same page
if ( user_not_administrator ) {
//TODO: redirect user to some error page and get yourself a notification
exit;
}
$redirect_uri = 'https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
function createMessage($sender, $to, $subject, $messageText) {
$message = new Google_Service_Gmail_Message();
$rawMessageString = "From: <{$sender}>\r\n";
$rawMessageString .= "To: <{$to}>\r\n";
$rawMessageString .= 'Subject: =?utf-8?B?' . base64_encode($subject) . "?=\r\n";
$rawMessageString .= "MIME-Version: 1.0\r\n";
$rawMessageString .= "Content-Type: text/html; charset=utf-8\r\n";
$rawMessageString .= 'Content-Transfer-Encoding: quoted-printable' . "\r\n\r\n";
$rawMessageString .= "{$messageText}\r\n";
$rawMessage = strtr(base64_encode($rawMessageString), array('+' => '-', '/' => '_'));
$message->setRaw($rawMessage);
return $message;
}
function sendMessage($service, $userId, $message) {
try {
return $service->users_messages->send($userId, $message);
} catch (Exception $e) {
//todo error - use $e->getMessage();
}
return null;
}
sendMessage(new Google_Service_Gmail($client), 'me', createMessage(...));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With