Gmail has been a long-time friend (probably) for developers to help in sending automated emails to users freely right from their systems. But of course the scale of the said systems is small to medium one, estimated up to several tens or hundreds of active users yearly. To be able to use Gmail account in the system, developers simply had to use the email address and password in the mail configuration (using a mailing library) in their projects. But this kind of traditional method has long been criticized because of its unsecure nature, where we had to store the password in separate environment variable or hard-coded it in the code just for the baddie to be see it easily.
In 2024, Google has decided to proceed the transition of authentication method for Gmail accounts from traditional passwords to OAuth2 (details here). This policy has forced many users, especially developers, to refactor their existing systems to be able to use OAuth2 (XOAUTH2) for authenticating Gmail accounts that are being used as system account for mailing purposes. Once a feature called "Less secure apps" access for legacy systems was active in Google Account settings for people to be able to transition to newer method, now it has been effectively removed.
You can read this documentation about how OAuth2 (XOAUTH2) method works.
Open Journal System (OJS) is one of the systems that still use the traditional method for mailing service. Since the change has been implemented throughout the year, people using OJS gradually catch on the issue, which their OJS system can not send automated emails. Have learnt about the Google's change in policy and already mitigate the issue myself before the implementation, I will share what you need to do to update your OJS configuration, so it can be able to send emails with Gmail account once again with OAuth2.
Prerequisites
This workaround is for you if:
- Your organization is using Google Workspace. Therefore, the Gmail account which being used for automated mailing purposes is created through Google Workspace.
- You have administrative access to Google Cloud resources of your organization (should be automatically associated with the Google Workspace) and credentials of the Gmail account.
- You hosted the OJS systems either in Google Cloud's organization (much better) or other platforms.
- You are using OJS version 3.
- You have sufficient understanding about how PHP project works.
Step 1: Go to Google Cloud Console
Visit Google Cloud Console of your organization. Make sure you are in the Project that contains your hosted OJS if you hosted your OJS with Google Cloud. For you who don't use Google Cloud for hosting, still you can create a Project here first without actually creating VM or any compute solutions.
Step 2: Create OAuth client ID
This steps assumed that you have created an OAuth consent screen. If you haven't, you need to go to the OAuth consent screen first, then fill in information as required. No additional configuration needed there.
- Navigate to APIs & Services > Credentials.
- Click on + Create credentials button, then click on OAuth client ID.
- In the creation form of OAuth client ID page, choose Web application under Application type dropdown. Give name of the client, e.g. "OJS".
- Under Authorized redirect URIs section, click on + Add URI button, then type in the full URI of OJS' PHPMailer's OAuth handler script, it should be like:
https://[YOUR OJS DOMAIN]/lib/pkp/lib/vendor/phpmailer/phpmailer/get_oauth_token.php
- Click Create.
- After creation of OAuth client ID, you can always go back to the Credentials page and click on the created OAuth client ID to see Additional information and client secrets, which some of this values will be added to the OJS. Take note of:
- Client ID
- Client secret
Step 3: Modify the get_oauth_token.php
script
You need to get into the directory of previous URI and edit the get_oauth_token.php
directly. Please compare the existing script with the updated version below, before make any changes to yours. Create a backup first!
<?php
/**
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5
* @package PHPMailer
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
/**
* Get an OAuth2 token from an OAuth2 provider.
* * Install this script on your server so that it's accessible
* as [https/http]://<yourdomain>/<folder>/get_oauth_token.php
* e.g.: http://localhost/phpmailer/get_oauth_token.php
* * Ensure dependencies are installed with 'composer install'
* * Set up an app in your Google/Yahoo/Microsoft account
* * Set the script address as the app's redirect URL
* If no refresh token is obtained when running this file,
* revoke access to your app and run the script again.
*/
namespace PHPMailer\PHPMailer;
/**
* Aliases for League Provider Classes
* Make sure you have added these to your composer.json and run `composer install`
* Plenty to choose from here:
* @see http://oauth2-client.thephpleague.com/providers/thirdparty/
*/
// @see https://github.com/thephpleague/oauth2-google
use League\OAuth2\Client\Provider\Google;
// @see https://packagist.org/packages/hayageek/oauth2-yahoo
use Hayageek\OAuth2\Client\Provider\Yahoo;
// @see https://github.com/stevenmaguire/oauth2-microsoft
use Stevenmaguire\OAuth2\Client\Provider\Microsoft;
if (!isset($_GET['code']) && !isset($_GET['provider'])) {
?>
<html>
<body>Select Provider:<br/>
<a href='?provider=Google'>Google</a><br/>
<a href='?provider=Yahoo'>Yahoo</a><br/>
<a href='?provider=Microsoft'>Microsoft/Outlook/Hotmail/Live/Office365</a><br/>
</body>
</html>
<?php
exit;
}
// require 'vendor/autoload.php';
// require($_SERVER['DOCUMENT_ROOT'] . '/lib/pkp/lib/vendor/autoload.php');
// Below is the new location of autoload
require('/var/www/lib/vendor/autoload.php');
session_start();
$providerName = '';
if (array_key_exists('provider', $_GET)) {
$providerName = $_GET['provider'];
$_SESSION['provider'] = $providerName;
} elseif (array_key_exists('provider', $_SESSION)) {
$providerName = $_SESSION['provider'];
}
if (!in_array($providerName, ['Google', 'Microsoft', 'Yahoo'])) {
exit('Only Google, Microsoft and Yahoo OAuth2 providers are currently supported in this script.');
}
//These details are obtained by setting up an app in the Google developer console,
//or whichever provider you're using.
$clientId = '[YOUR CLIENT ID HERE]';
$clientSecret = '[YOUR CLIENT SECRET HERE]';
//If this automatic URL doesn't work, set it yourself manually to the URL of this script
//$redirectUri = (isset($_SERVER['HTTPS']) ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
$redirectUri = 'https://[YOUR OJS DOMAIN]/lib/pkp/lib/vendor/phpmailer/phpmailer/get_oauth_token.php';
$params = [
'clientId' => $clientId,
'clientSecret' => $clientSecret,
'redirectUri' => $redirectUri,
'accessType' => 'offline'
];
$options = [];
$provider = null;
switch ($providerName) {
case 'Google':
$provider = new Google($params);
$options = [
'scope' => [
'https://mail.google.com/'
]
];
break;
case 'Yahoo':
$provider = new Yahoo($params);
break;
case 'Microsoft':
$provider = new Microsoft($params);
$options = [
'scope' => [
'wl.imap',
'wl.offline_access'
]
];
break;
}
if (null === $provider) {
exit('Provider missing');
}
if (!isset($_GET['code'])) {
// If we don't have an authorization code then get one
$authUrl = $provider->getAuthorizationUrl($options);
$_SESSION['oauth2state'] = $provider->getState();
header('Location: ' . $authUrl);
exit;
// Check given state against previously stored one to mitigate CSRF attack
} elseif (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
unset($_SESSION['oauth2state']);
unset($_SESSION['provider']);
exit('Invalid state');
} else {
unset($_SESSION['provider']);
// Try to get an access token (using the authorization code grant)
$token = $provider->getAccessToken(
'authorization_code',
[
'code' => $_GET['code']
]
);
// Use this to interact with an API on the users behalf
// Use this to get a new access token if the old one expires
echo 'Refresh Token: ', $token->getRefreshToken();
}
Step 4: Replace vendor
packages manually
Due to the obsolete packages being used by this particular script, you actually need to update the dependencies of this PHPMailer package. However, it may be complicated since it's also a dependency for OJS. For this workaround, you can use my vendor
packages from here. Extract this archive into a separate directory aside of your OJS in the server, e.g. /var/www/lib
(as I pointed out in the PHP script above).
If you want to get yourself the new vendor
folder, you may set up a local PHP project (just create an empty folder anywhere you want). Inside this folder, run below command:
composer require league/oauth2-google
After adding the dependencies, you can archive the vendor
directory in the local project and upload it to your OJS server. Then, extract the archive into separate folder, accessible by the OJS like above mentioned, e.g. /var/www/lib
.
Step 5: Update Mail.inc.php
dependency
Put this require
line at the first lines of the Mail.inc.php
script located in lib/pkp/classes/mail
, right before the actual code.
require('/var/www/lib/vendor/autoload.php');
This line help to make sure the Mail
class can load dependencies right to the PHPMailer package.
Step 6: Retrieve a Refresh Token
After setting things up, you need to visit the URI of get_oauth_token.php
, so it should be: https://[YOUR OJS DOMAIN]/lib/pkp/lib/vendor/phpmailer/phpmailer/get_oauth_token.php
. In this page, click Google link and you may be prompted with Google Login page. Login with your purposed Gmail account.
You can see a line of refresh token after a successful login. Copy this value and paste somewhere else. We need this refresh token for new configuration of OJS.
Step 7: Add XOAUTH2 configuration to config.inc.php
Edit the config.inc.php
in the root directory of your OJS, focus on Email settings section here. Adjust the configuration like this example:
[email]
; Use SMTP for sending mail instead of mail()
smtp = On
; SMTP server settings
smtp_server = smtp.gmail.com
smtp_port = 587
; Enable SMTP authentication
; Supported mechanisms: ssl, tls
smtp_auth = tls
smtp_username = [YOUR GMAIL ADDRESS HERE]
smtp_password = [YOUR GMAIL PASSWORD HERE]
smtp_authtype = XOAUTH2
smtp_oauth_provider = Google
smtp_oauth_email = [YOUR GMAIL ADDRESS HERE]
smtp_oauth_clientid = [YOUR OAUTH2 CLIENT ID HERE]
smtp_oauth_clientsecret = [YOUR OAUTH2 CLIENT SECRET HERE]
smtp_oauth_refreshtoken = '[YOUR OAUTH2 REFRESH TOKEN HERE]'
; Allow envelope sender to be specified
; (may not be possible with some server configurations)
allow_envelope_sender = On
; Default envelope sender to use if none is specified elsewhere
default_envelope_sender = [YOUR GMAIL ADDRESS HERE]
; Force the default envelope sender (if present)
; This is useful if setting up a site-wide no-reply address
; The reply-to field will be set with the reply-to or from address.
force_default_envelope_sender = On
Step 8: Test the mailing feature
Finally, you can test the changes you have made so far by creating a dummy submission in your OJS website, or try anything else that can trigger the outgoing mail action from the site.
Conclusion
OJS has been used by many institutions for years to help managing scienfitic articles and publications. The dynamics from Google especially in the rise of a more secure method to authenticate email addresses using OAuth2 make legacy systems like OJS has to change, where email notifications are one of the most important feature for users may be disrupted if they are using Gmail. While there's maybe difficulties faced by institutions to update their OJS systems following the official documentation, when they are using Gmail accounts to send emails, we can still use some workaround here. Not the best one, but I hope it can help you get there.