flysystem-dropbox icon indicating copy to clipboard operation
flysystem-dropbox copied to clipboard

Access token expiration

Open anderea1 opened this issue 2 years ago • 5 comments

Hi, I am using spatie/flysystem-dropbox (^1.2) and I get dropbox directories with: $dirs = Storage::disk('dropbox')->directories(); or files with: $files = Storage::disk('dropbox')->files($foldername);

I wrote these 2 variable in .env file: DROPBOX_ACCESS_TOKEN=sl.BLOC_r... DROPBOX_APP_SECRET=...

I got DROPBOX_ACCESS_TOKEN here https://www.dropbox.com/developers/apps

After 4 hours I get this error:

Client error: POST https://api.dropboxapi.com/2/files/list_folder resulted in a 401 Unauthorized response: {"error_summary": "expired_access_token/.", "error": {".tag": "expired_access_token"}} {"exception":"[object] (GuzzleHttp\Exception\ClientException(code: 401): Client error: POST https://api.dropboxapi.com/2/files/list_folder resulted in a 401 Unauthorized response: {"error_summary": "expired_access_token/.", "error": {".tag": "expired_access_token"}} at /home/xxx/laravel/vendor/guzzlehttp/guzzle/src/Exception/RequestException.php:113)

I manually solve it going back to https://www.dropbox.com/developers/apps, generating a new token and writing it in .env. How can I avoid this manual step?

anderea1 avatar Jul 11 '22 15:07 anderea1

A workmate helped me: instead of using an access token we manually got a refresh token (it should never expire) and used it. To get a refresh token see the accepted solution here: https://www.dropboxforum.com/t5/Dropbox-API-Support-Feedback/Issue-in-generating-access-token/td-p/59266

.env:

DROPBOX_APP_KEY=xxx DROPBOX_APP_SECRET=xxx DROPBOX_REFRESH_TOKEN=xxx

config\app.php:

'providers' => [
    // ...       
    App\Providers\DropboxServiceProvider::class,   
]

config\filesystems.php:

'disks' => [
   // ... 
   'dropbox' => [
        'driver' => 'dropbox',
        'refreshToken' => env('DROPBOX_REFRESH_TOKEN'),
        'appKey' => env('DROPBOX_APP_KEY'),
        'appSecret' => env('DROPBOX_APP_SECRET'),
    ],
]

app\Adapters\AutoRefreshingDropBoxTokenService.php:

<?php
namespace App\Adapters;
class AutoRefreshingDropBoxTokenService
{
    public function getToken($key, $secret, $refreshToken)
    {
        try {
            $client = new \GuzzleHttp\Client();
            $res = $client->request("POST", "https://{$key}:{$secret}@api.dropbox.com/oauth2/token", [
                'form_params' => [
                    'grant_type' => 'refresh_token',
                    'refresh_token' => $refreshToken,
                ]
            ]);
            if ($res->getStatusCode() == 200) {
                return json_decode($res->getBody(), TRUE)['access_token'];
            } else {
                info(json_decode($res->getBody(), TRUE));
                return false;
            }
        } catch (\Exception $e) {
            info($e->getMessage());
            return false;
        }
    }
}

app\Providers\DropboxServiceProvider.php:

<?php
namespace App\Providers;
use Storage;
use League\Flysystem\Filesystem;
use Illuminate\Support\ServiceProvider;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;
use App\Adapters\AutoRefreshingDropBoxTokenService;
class DropboxServiceProvider extends ServiceProvider
{
    public function register()
    {
        //
    }
    public function boot()
    {
        Storage::extend('dropbox', function ($app, $config) {
            $token = new AutoRefreshingDropBoxTokenService;
            $client = new DropboxClient($token->getToken($config['appKey'], $config['appSecret'], $config['refreshToken']));
            return new Filesystem(new DropboxAdapter($client), ['case_sensitive' => false]);
        });
    }
}

Use example:

use Storage;
use Illuminate\Filesystem\Filesystem; // needed?
//...
$dirs = Storage::disk('dropbox')->directories();
foreach ($dirs as $dir) {
    $files = Storage::disk('dropbox')->files($dir);
    foreach ($files as $file) { // $file contains the folder name
        $array_temp = explode('/', $file);
        $filename = $array_temp[count($array_temp) - 1];
        // copy file in local storage					
	Storage::disk('local')->writeStream( 'local_folder/' . $filename, Storage::disk('dropbox')->readStream( $file ));
	// delete dropbox file
	Storage::disk('dropbox')->delete($file);
    }
}					

anderea1 avatar Jul 13 '22 18:07 anderea1

@anderea1 The refresh token how did you generate it?

The refresh_token is the same one you generated in DROPBOX_ACCESS_TOKEN here https://www.dropbox.com/developers/apps

JorgeSolisC avatar Aug 12 '22 17:08 JorgeSolisC

I get a 'Refresh token is malformed' error when using the token generated in the dropbox app console, as the refresh token curl https://api.dropbox.com/oauth2/token -d grant_type=refresh_token -d refresh_token=refresh-token -u appkey:secret

{"error": "invalid_grant", "error_description": "refresh token is malformed"}

Still unsure how to get the refresh token described above.

karunkshrestha avatar Sep 05 '22 16:09 karunkshrestha

I'm also getting the 'token is malformed' error

fri3ndly avatar Oct 03 '22 15:10 fri3ndly

I just generated a refresh token using this guide: https://gist.github.com/phuze/755dd1f58fba6849fbf7478e77e2896a

fri3ndly avatar Oct 03 '22 16:10 fri3ndly

Dear contributor,

because this issue seems to be inactive for quite some time now, I've automatically closed it. If you feel this issue deserves some attention from my human colleagues feel free to reopen it.

spatie-bot avatar Feb 06 '23 11:02 spatie-bot

app\Providers\DropboxServiceProvider.php:

namespace App\Providers;

use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;
use App\Adapters\AutoRefreshingDropBoxTokenService;

class DropboxServiceProvider extends ServiceProvider
{

    public function register()
    {
        //
    }


    public function boot()
    {
        Storage::extend('dropbox', function ($app, $config) {
            $token = new AutoRefreshingDropBoxTokenService;
            $client = new DropboxClient(
                $token->getToken($config['appKey'],
                $config['appSecret'],
                $config['refreshToken'])
            );
            $adapter = new DropboxAdapter($client);
            $driver = new Filesystem($adapter, ['case_sensitive' => false]);

            return new FilesystemAdapter($driver, $adapter);
        });
    }
}

lowv-developer avatar Mar 14 '23 01:03 lowv-developer

I tried something else personally, without using AutoRefreshingDropBoxTokenService [ not necessary when using the flysystem-dropbox ] and it is less verbose :

You will still need to authorize the access to the Dropbox App using this link :

https://www.dropbox.com/oauth2/authorize?client_id<YOUR_APP_KEY>&response_type=code&token_access_type=offline

This will give you the authorization_code needed to retrieve a refresh_token with this curl request :

curl https://api.dropbox.com/oauth2/token -d code=<ACCESS_CODE> -d grant_type=authorization_code -u <APP_KEY>:<APP_SECRET>

Giving you access to the refresh_token needed in the DROPBOX_REFRESH_TOKEN indicated below . The other elements are available in your Dropbox App.

.env.example

DROPBOX_APP_KEY=
DROPBOX_APP_SECRET=
DROPBOX_REFRESH_TOKEN=
DROPBOX_TOKEN_URL=https://${DROPBOX_APP_KEY}:${DROPBOX_APP_SECRET}@api.dropbox.com/oauth2/token

config/filesystems.php

        'dropbox' => [
            'driver' => 'dropbox',
            'token_url' => env('DROPBOX_TOKEN_URL'),
            'refresh_token' => env('DROPBOX_REFRESH_TOKEN'),
        ],

`app/Providers/DropboxServiceProvider.php

use Illuminate\Contracts\Foundation\Application;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;
use Spatie\Dropbox\Client as DropboxClient;
use Spatie\FlysystemDropbox\DropboxAdapter;
use GuzzleHttp\Client;


class DropboxServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Storage::extend( 'dropbox', function( Application $app, array $config )
        {
            $resource = ( new Client() )->post( $config[ 'token_url' ] , [
                    'form_params' => [ 
                            'grant_type' => 'refresh_token', 
                            'refresh_token' => $config[ 'refresh_token' ] 
                    ] 
            ]);

            $accessToken = json_decode( $resource->getBody(), true )[ 'access_token' ];

            $adapter = new DropboxAdapter( new DropboxClient( $accessToken ) );

            return new FilesystemAdapter( new Filesystem( $adapter, $config ), $adapter, $config );
        });
    }
}

A PR in code and documentation should be interesting. Should I PR this ? The previous code is also indicated in the Laravel 10 Documentation - Custom Filesystems

mho22 avatar Apr 25 '23 00:04 mho22

@Itemshopp Exactly what I needed, thanks so much for sharing.

chris-arated avatar Jul 17 '23 09:07 chris-arated