2

PROBLEM SOLVED

Finally, I get the refresh_token, by revoking from google account(aka remove the apps access through account).

Everything goes normal again, and I don't need to solve the problem as the title 'Get Google access token not through the browser'.

But why I success this time? Because this time I use curl but not thephpleague/oauth2-client way to request auth. I think there must missing some parms when I request auth via thephpleague/oauth2-client.

Here is a curl way that I get the refresh_token EVERYTIME after I revoke/remove the apps access from account.

<?
//Reference to dbjpanda/google-api.php(https://gist.github.com/dbjpanda/0ba3d73832b25d720398e8f1dce1359b)

$client_id = 'xxx.apps.googleusercontent.com';
$client_secret = 'xxxx';
$redirect_uri = 'https://example.com/get_token.php';
$end_point = 'https://accounts.google.com/o/oauth2/v2/auth';
$token_file="my-token.json";

$authUrl = $end_point.'?'.http_build_query([
    'client_id'              => $client_id,
    'redirect_uri'           => $redirect_uri,
    'scope'                  => 'https://mail.google.com/',
    'access_type'            => 'offline',
    'include_granted_scopes' => 'true',
    'response_type'          => 'code',
]);

echo '<a href = "'.$authUrl.'">Authorize</a></br>';

if ( !file_exists($token_file) ){
    
    if ( isset($_GET['code'])){
        $code = $_GET['code'];
    }else{
        return;
    } 

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL,"https://accounts.google.com/o/oauth2/token");
    curl_setopt($ch, CURLOPT_POST, TRUE);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/x-www-form-urlencoded']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
        'code'          => $code,
        'client_id'     => $client_id,
        'client_secret' => $client_secret,
        'redirect_uri'  => $redirect_uri,
        'grant_type'    => 'authorization_code',
    ]));
    
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    $response = curl_exec($ch);
    curl_close ($ch);
    
    file_put_contents($token_file, $response);

}else{
    //and then to use the refresh_token to get a new access_token
}

UPDATED THE QUESTION

In short, how to get google OAuth access token automatically not through the browser(that is header('Location: '))?

I cannot get the refresh token any more, because I exceed the access limit while developing, and I've tried many ways and still cannot get the refresh token.

ORIGINAL QUESTION

I've successfully set up get_token.php to get Google OAuth2 only for my website(example.com) for PHPMailer, and I can get access token via browser(by select my google account to log in to access).

Because I have exceed the limit of refresh token via development, so I've tried to use another google account to create api for my website(example.com), revoke from google account, delete OAuth and create another one(so there is a new clientId); but none of them work, and I think that I will never get a refresh token again.

Because my website is running and keep sending emails, so every hour I have to manually access the access token by link to https://example.com/get_token.php through the browser, so the PHPMailer can use the access token to work.

Is there a way to get refresh token again, or automatically link to get_token.php to access the access token?ex: use command line, so the cronJob will do it on every hour.

My get_token.php is very similar to the example shows on the document of thephpleague/oauth2-client but I don't get the refresh_token

<?
require 'vendor/autoload.php';

session_start();

$token_file="my-token.json";

$provider = new \League\OAuth2\Client\Provider\Google([
    'clientId'                => 'xxx.apps.googleusercontent.com',
    'clientSecret'            => 'password',
    'redirectUri'             => 'https://example.com/get_token.php',
    'scopes' => ['https://mail.google.com/'],
    'access_type' => 'offline'
]);

if (!isset($_GET['code'])) {

    $authorizationUrl = $provider->getAuthorizationUrl();

    $_SESSION['oauth2state'] = $provider->getState();

    header('Location: ' . $authorizationUrl);
    exit;

} elseif (empty($_GET['state']) || (isset($_SESSION['oauth2state']) && $_GET['state'] !== $_SESSION['oauth2state'])) {

    if (isset($_SESSION['oauth2state'])) {
        unset($_SESSION['oauth2state']);
    }

    exit('Invalid state');

} else {

    try {

        $accessToken = $provider->getAccessToken('authorization_code', [
            'code' => $_GET['code']
        ]);

        $token=json_decode(file_get_contents($token_file));
        $token->access_token=$accessToken->getToken();

        if(!empty($accessToken->getRefreshToken())){
            $token->refresh_token=$accessToken->getRefreshToken();
        }

        file_put_contents($token_file, json_encode($token));
        
    } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {

        exit($e->getMessage());

    }

}

Here is my Mail.php

$token_file="my-token.json";
$mail = new PHPMailer();

try {

    $mail->IsSMTP();
    $mail->SMTPAuth = true;
    $mail->SMTPSecure = "tls";
    $mail->Host = "smtp.gmail.com";
    $mail->Port = 587;
    $mail->AddAddress($_GET['to']);
        
    $oauth_token=file_get_contents($token_file);
    $refresh_token= json_decode($oauth_token)->refresh_token;
                  
    $mail->AuthType ="XOAUTH2";
    $mail->oauthUserEmail = 'auto-mail@example.com';
    $mail->oauthClientId = "xxx.apps.googleusercontent.com";
    $mail->oauthClientSecret = "password";
    $mail->oauthRefreshToken = $refresh_token;

    $mail->CharSet = 'utf-8';
    $mail->SMTPDebug = 2;
    $mail->Encoding   = "base64";

    return $mail->Send();

} catch (Exception $e) {
    return false;
}
Autodesk
  • 631
  • 8
  • 27
  • Because I have exceed the limit of refresh token via development <-- What limit would that be exactly? In test a refresh token will work for a week. Im also confused as to why you are setting oauthRefreshToken to an access token and not a refresh token.. – Linda Lawton - DaImTo Mar 16 '22 at 09:15
  • @DaImTo I google it, it seems the limit is 50 times, because now no matter how I get, I only receive access token, and don't receive refresh token, that's why I can only use access token to do so – Autodesk Mar 16 '22 at 09:35
  • @DaImTo this is about not receiving refresh token: https://stackoverflow.com/questions/10827920/not-receiving-google-oauth-refresh-token – Autodesk Mar 16 '22 at 09:38
  • Actually its about a lot of things, sorry about the wall of text answer. Let me know if there's anything else you don't understand and i will expand it more. – Linda Lawton - DaImTo Mar 16 '22 at 10:04

1 Answers1

3

First issue: off I think you have miss understood something about refresh tokens.

If you check the expiration page you will set this

There is currently a limit of 50 refresh tokens per Google Account per OAuth 2.0 client ID. If the limit is reached, creating a new refresh token automatically invalidates the oldest refresh token without warning. This limit does not apply to service accounts.

Yes there is a limit of 50 OUTSTANDING refresh tokens for a user. This means if I run your app and authorize it i get a refresh token back. If i run it again i get another refresh token back. I can do this 50 times and your app will have 50 outstanding refresh tokens to my account and they all will work. Once i do it the 51st time the first one will be expired.

So you can have a max of 50 out standing refresh tokens per user.

Second issue: Google does not return a new refresh token to you with every authorization request for a user. (Don't ask me why. It appears to be language specific. C# and java get a new token back every time. PHP and Python don't appear to.) Google assumes that you have stored that refresh token. I think if you do prompt=consent in the requested it will force it to return to you a new one. You can also have the user revoke your access to their app though there google account this would also trigger a new consent and return an access token and refresh token.

If prompt=consent doesn't work then go though this link and remove the apps access from your google account.

Third issue: Refresh tokens and access tokens are not interchangeable. A refresh token is used to request a new access token. an access token is used to access an api.

So by doing this

$access_token= json_decode($oauth_token)->access_token;
              
$mail->AuthType ="XOAUTH2";
$mail->oauthUserEmail = 'auto-mail@example.com';
$mail->oauthClientId = "xxx.apps.googleusercontent.com";
$mail->oauthClientSecret = "password";
$mail->oauthRefreshToken = $access_token;

Its going to fail because it will try to use an access token to refresh the access token which will not work.

$refresh_token= json_decode($oauth_token)->refresh_token;
              
$mail->AuthType ="XOAUTH2";
$mail->oauthUserEmail = 'auto-mail@example.com';
$mail->oauthClientId = "xxx.apps.googleusercontent.com";
$mail->oauthClientSecret = "password";
$mail->oauthRefreshToken = $refresh_token;
Linda Lawton - DaImTo
  • 106,405
  • 32
  • 180
  • 449
  • thanks for the reply, I've used `prompt=consent` and revoke, but still not getting the refresh token. I really ask the refresh token too many times while developing :( – Autodesk Mar 16 '22 at 12:32
  • I use `$mail->oauthRefreshToken = $refresh_token;` in the begging, and since I cannot access the refresh_token, so there is no refresh_token on my json file , and then I accidently use the access_token, and it works! That's weird, but it really works LOL, so I temporarily use it. I hope that I can access the refresh_token again, or find a way to access the access_token not through the browser. – Autodesk Mar 16 '22 at 12:38
  • I've tried prompt=consent, prompt=consent and access_type=offline, access_type=offline, to use another google account to create api for my website(example.com), revoke from google account, delete OAuth and create another one to generate a new clientId, but still cannot get the refresh_token. I think I should try these ways again. – Autodesk Mar 16 '22 at 12:47
  • Thank you for the `limit of 50` explanation, so it does not relate to not getting refresh_token! – Autodesk Mar 16 '22 at 12:50
  • Go though https://support.google.com/accounts/answer/3466521?hl=en and remove the apps access though your account. You need to get back a refresh token. – Linda Lawton - DaImTo Mar 16 '22 at 14:26
  • I get the refresh_token, finally, by revoking from google account(aka remove the apps access through account). But why I success this time? Because this time I use `curl` but not `thephpleague/oauth2-client` to request auth. – Autodesk Mar 17 '22 at 02:52
  • I've tried few more time after revoke/remove, and I always receive the refresh_token via `curl`, but I still don't get refresh_token via `thephpleague/oauth2-client`. When I look back, it seems it was when I started to use `thephpleague/oauth2-client` that I stopped to receive the refresh_token, I think there must some parms missing when I request auth. – Autodesk Mar 17 '22 at 02:53
  • With the refresh_token, everything goes normal again, and I don't need to search a way to automatically get access_token. – Autodesk Mar 17 '22 at 02:54
  • Thank you @DalmTo help me clarify many things, and now I have to face another problem: the refresh_token expire issue......LOL – Autodesk Mar 17 '22 at 02:57
  • 2
    Set your project to production instead of test and the refresh token will stop expiring. – Linda Lawton - DaImTo Mar 17 '22 at 06:59
  • If I set the user type to Internal, does the refresh_token expire? – Autodesk Mar 19 '22 at 16:24