【Dropbox】【Laravel】Dropboxとの通信で expired_access_token とエラーが返って来てしまう時の対処

 Dropxboxはクラウドストレージサービスの一つです。Dropboxを利用してファイルの保存、共有などができます。LaravelでDropboxを楽に使うためのライブラリであるspatie/flysystem-dropboxを紹介します。

spatie/flysystem-dropbox: A flysystem driver for Dropbox that uses the v2 API

 spatie/flysystem-dropboxはLaravelのファイル操作機能とDropboxをつなげることで他のファイルをどうこうする処理と同じ書き方をできるようにするライブラリです。ライブラリが提供している範囲がFileSystemまでで、Storageから呼び出すにはちょっとコードの追加が必要になりますが、直にAPIを使ってあれこれするよりずっと楽です。

 spatie/flysystem-dropboxを使って通信が成功していたのに expired_access_token エラー、いわゆるトークンの寿命切れのエラーが出てくる時があります。これは永続的なトークンを利用する方式でなく一時的なトークンを直に渡す方式で spatie/flysystem-dropbox を使った場合に起きやすいです。これの対策のためにリフレッシュトークンというアクセストークンを再発行できる永続的なトークンを利用して通信する方法があります。ちなみにこのトークンのやり取りのフローは「リフレッシュトークングラントフロー」で検索すると参考になる資料が多々見つかります。

 まずはプログラムの環境ファイルに配置するためのリフレッシュトークンをDropboxのアプリ機能を利用して取得する手順を紹介します。この手順で取得したリフレッシュトークンとアプリの設定画面にあるApp keyとApp secretを使うことでトークンの寿命を気にせず通信できるようになります。

  1. 次のアプリ設定画面のスクリーンショットの赤丸部にあるApp keyをあてはめて次のURLにブラウザ上からアクセスします。

    https://www.dropbox.com/oauth2/authorize?client_id={App key}&response_type=code&token_access_type=offline
    
  2. ブラウザ上で開かれたページで許可をして、アクセスコードを取得します。
  3. アクセスコードを利用して次のようにPOSTでアクセスします。例ではcURLですが、どのクライアントを使っても大丈夫です。
    curl https://api.dropbox.com/oauth2/token -d code={アクセスコード} -d grant_type=authorization_code -u {App key}:{App secret}
    
  4. 返ってきたJSONレスポンスを保存。このレスポンスの中のrefresh_tokenがリフレッシュトークンです。

 次いでLaravelの方のコードです。これは次のようにできます。

 まずは設定です。必要なパラメータをDropboxのそれとわかる名前にしてconfigから呼べるようにします。

# .env
# App keyの値
DROPBOX_APP_KEY=xxxxxxxxxxxxxxx
# App secretの値
DROPBOX_APP_SECRET=xxxxxxxxxxxxxxx
# 取得したリフレッシュトークン
DROPBOX_REFRESH_TOKEN=xxxxxx_xxxxxxxxxxxxxxxxxxx-xxx-xxxxxxxxxxx_xxxxxxxx-xxxxxxxxxxxx
<?php
// config/filesystems.php
// ファイルシステムの拡張なので既存のファイルシステム設定ファイル内に追記する方が無難です

return [
    'disks'   => [
        'dropbox' => [
            'driver'       => 'dropbox',
            'app_key' => env('DROPBOX_APP_KEY'),
            'app_secret' => env('DROPBOX_APP_SECRET'),
            'refresh_token' => env('DROPBOX_REFRESH_TOKEN'),
        ],
    ],
];

 次いでリフレッシュトークンを利用した通信部です。spatie/flysystem-dropboxにはトークン文字列のみでなくトークンを提供するクラスからトークンを読み取る機能も備わっています。これを利用します。

<?php
// app/Service/Dropbox/AutoRefreshingDropboxTokenService.php

namespace App\Service\Dropbox;

use Spatie\Dropbox\TokenProvider;

// TokenProviderインターフェースを実装するAutoRefreshingDropboxTokenServiceクラスです。
class AutoRefreshingDropboxTokenService implements TokenProvider
{
    // コンストラクタです。Dropbox APIのキー、シークレット、リフレッシュトークンをプライベート変数として保持します。
    public function __construct(
        private readonly string $key,
        private readonly string $secret,
        private readonly string $refreshToken,
    ) {
    }

    // アクセストークンを取得するためのメソッドです。
    public function getToken(): string
    {
        // HTTPクライアントを生成します。
        $client = new \GuzzleHttp\Client();
        // DropboxのAPIにアクセスしてトークンを更新するリクエストを送信します。
        $res = $client->request("POST", "https://{$this->key}:{$this->secret}@api.dropbox.com/oauth2/token", [
            'form_params' => [
                'grant_type'    => 'refresh_token', // リフレッシュトークンを使って新しいアクセストークンを要求することを指定します。
                'refresh_token' => $this->refreshToken, // 実際のリフレッシュトークンの値を指定します。
            ]
        ]);
        // リクエストが成功したかどうかのステータスコードをチェックします。
        if($res->getStatusCode() == 200) {
            // 成功した場合、新しいアクセストークンを返します。
            return json_decode($res->getBody(), true)['access_token'];
        } else {
            // 失敗した場合、エラーを投げます
            throw new \RuntimeException($res->getBody());
        }
    }
}
<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        // Storageファサードのextendメソッドを使用して、'dropbox'という名前のカスタムドライバを定義します。
        Storage::extend('dropbox', function (Application $app, array $config) {
            // 設定→AutoRefreshingDropboxTokenService→DropboxClient→DropboxAdapterとリレーしてDropboxAdapterをインスタンス化します
            $tokenProvider = new AutoRefreshingDropboxTokenService(
                $config['app_key'],
                $config['app_secret'],
                $config['refresh_token']
            );
            $client = new \Spatie\Dropbox\Client($tokenProvider);
            $adapter = new \Spatie\FlysystemDropbox\DropboxAdapter($client);
            // DropboxAdapterを使って、新しいFilesystemAdapterを作成して返します。
            // これによりLaravelのファイルシステム機能でDropboxを使用できるようになります。
            return new FilesystemAdapter(
                new Filesystem($adapter, $config),
                $adapter,
                $config
            );
        });
    }
}

 こんな感じでトークンの寿命問題を解決しつつspatie/flysystem-dropboxを用いることできます。

>株式会社シーポイントラボ

株式会社シーポイントラボ

TEL:053-543-9889
営業時間:9:00~18:00(月〜金)
住所:〒432-8003
   静岡県浜松市中央区和地山3-1-7
   浜松イノベーションキューブ 315
※ご来社の際はインターホンで「316」をお呼びください

CTR IMG