Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How can I refresh the access token? #115

Open
johnRivs opened this issue Aug 15, 2022 · 6 comments
Open

How can I refresh the access token? #115

johnRivs opened this issue Aug 15, 2022 · 6 comments

Comments

@johnRivs
Copy link

I'm using this adapter inside a queue. Once it's registered via Storage::extend(), it'll be reused as long as the queue is running. Unfortunately, the access token expires in 1 hour and I can't seem to refresh it.

What I was trying to do was to make a singleton out of the Google client used by the adapter then set up a scheduler task every 30 minutes where I do this app('googleClient')->refreshToken(config('filesystems.disks.google.refreshToken')); then cache the credentials and finally app('googleClient')->setAccessToken(cache('the access token')) on every job I put in the queue. Didn't work. I've also tried to refresh the token on every job and it doesn't work either.

@sr2ds
Copy link

sr2ds commented Nov 3, 2022

Hello @johnRivs ,

I'm with the same issue, maybe we can think together.

I tried cleanup from tinker with:

Storage::disk('google')->getDriver()->getAdapter()->getService()->getClient()->getCache()->clear()

And don't works, but let's think in some points.

I saw that GoogleAPI package did use the PSR CacheItemPoolInterface to deal with the cache.
I don't sure if, when the process queue:worker is running, it use the same cache of my tinker, I guess not.

I want mean that, I think is not about default cache defined of application with CACHE_DRIVER and the process running has your own cache.

I dont try yet, but if the JOB run the cleanup command with ->getCache()->clear(), this will cleanup the process cache of driver? What do you think?

One bad way to fix it fast, is try run queue:restart inside our job. Because, when the worker is restarted, works. Because, I guess, the service provider is registered from zero.

@johnRivs
Copy link
Author

johnRivs commented Nov 3, 2022

This is what I ended up doing.

I have a class responsible for getting a new access token:

<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;

class Google
{
    static function refreshToken()
    {
        $credentials = app('googleClient')->refreshToken(config('filesystems.disks.google.refreshToken'));

        Cache::put('google-drive-credentials', $credentials);
    }
}

It's scheduled to run every 30 minutes:

<?php

namespace App\Console;

use App\Services\Google;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;

class Kernel extends ConsoleKernel
{
    function schedule(Schedule $schedule)
    {
        $schedule->call(function () {
            Google::refreshToken();
        })->everyThirtyMinutes();
    }
}

In EventServiceProvider.php, when a CompleteOrder job is put into the queue, I register the driver. When it's done processing, I remove the driver:

<?php

namespace App\Providers;

use App\Jobs\CompleteOrder;
use League\Flysystem\Filesystem;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage;
use Illuminate\Queue\Events\JobProcessed;
use Illuminate\Queue\Events\JobProcessing;
use Hypweb\Flysystem\GoogleDrive\GoogleDriveAdapter;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    function register()
    {
        $this->app->singleton('googleClient', function () {
            $client = new \Google_Client();

            $client->setClientId(config('filesystems.disks.google.clientId'));
            $client->setClientSecret(config('filesystems.disks.google.clientSecret'));

            return $client;
        });
    }

    function boot()
    {
        $this->prepareGoogleStorageForQueue();
        $this->registerGoogleStorage();
    }

    function prepareGoogleStorageForQueue()
    {
        Event::listen(JobProcessing::class, function($event) {
            if ($event->job->resolveName() === CompleteOrder::class) $this->registerGoogleStorage();
        });

        Event::listen(JobProcessed::class, function($event) {
            if ($event->job->resolveName() === CompleteOrder::class) Storage::forgetDisk('google');
        });
    }

    function registerGoogleStorage()
    {
        Storage::extend('google', function() {
            app('googleClient')->setAccessToken(cache('google-drive-credentials')['access_token']);

            $service = new \Google_Service_Drive(app('googleClient'));
            $adapter = new GoogleDriveAdapter($service);

            return new Filesystem($adapter);
        });
    }
}

@sr2ds
Copy link

sr2ds commented Nov 7, 2022

Hello my friend,
Thanks for share your way.

I merged my way and your way and looks good to run without scheduled task to update token.
I'm checking to ensure yet, but looks good.

I create one service to take about this:

<?php

namespace App\Services\Google;

use App\Providers\GoogleDriveAdapter;
use Storage;

class GoogelDriveStorageAuth
{
    public static  function reloadGoogleStorage()
    {
        $accessTokenExpired = Storage::disk('google')->getDriver()
            ?->getAdapter()
            ?->getService()
            ?->getClient()
            ->isAccessTokenExpired();

        if ($accessTokenExpired) {
            \Log::info('GoogelDriveStorageAuth: access token expired - Storage Reloaded');
            Storage::forgetDisk('google');
            self::registerStorage();
        }
    }

    public static function registerStorage()
    {
        \Storage::extend('google', function ($app, $config) {
            $app; // to fix grumphp settings -> variable not used
            $client = new \Google_Client();
            $client->setClientId($config['clientId']);
            $client->setClientSecret($config['clientSecret']);

            /**
             * Cache access token to reduce requests to Google
             */
            if (\Cache::has('google access token')) {
                $token = \Cache::get('google access token');
                $client->setAccessToken($token);
            }

            if ($client->isAccessTokenExpired()) {
                $refreshToken = $config['refreshToken'];
                if ($refreshToken) {
                    $token = $client->fetchAccessTokenWithRefreshToken($refreshToken);
                    $client->setAccessToken($token);
                    \Cache::add('google access token', $token);
                } else {
                    /**
                     * Invalid refresh token and or credentials
                     *
                     * Check cloud wiki on how to create an app and get credentials and refresh token
                     */
                    throw new \Exception("Invalid refresh token, check cloud wiki");
                }
            }

            $service = new \Google_Service_Drive($client);
            $options = [];
            $adapter = new GoogleDriveAdapter($service, $config['folderId'], $options);
            return new \League\Flysystem\Filesystem($adapter);
        });
    }
}

And, inside my Jobs, I put this line to, if necessary, reset the storage and re-auth:

GoogelDriveStorageAuth::reloadGoogleStorage();

Thanks

@johnRivs
Copy link
Author

johnRivs commented Nov 7, 2022

Yeah you can go that route as well.
I chose to set up a scheduled task so I wouldn't have to add that line to every job or make some of my jobs extend another base job where I do that in the constructor.
It felt more comfortable to write code anywhere assuming there's always a valid token available.

@sr2ds
Copy link

sr2ds commented Nov 7, 2022

Oh yes! Great! Good point!
Thanks

@parallels999
Copy link

parallels999 commented Nov 11, 2022

ivanvermeyen/laravel-google-drive-demo#107
https://github.com/masbug/flysystem-google-drive-ext/pull/34/files

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants