🔋

X API v2 リフレッシュトークンには有効期限がある

2024/12/02に公開

将来仕様が変わるかもしれないが、いったん備忘録として。(2024/12/1現在)

結論

X API OAuth 2.0 トークンエンドポイント POST /oauth2/tokengrant_type=refresh_token を使ってトークンをリフレッシュした場合、リフレッシュトークンの有効期限も一旦リセットされる。X公式ドキュメントだとこのあたりの仕様は不明瞭。
https://developer.x.com/en/docs/authentication/oauth-2-0/user-access-token

Response
{
    "token_type":"bearer",
    "expires_in":7200,
    "access_token":"ANYSTRING",
    "scope":"users.read tweet.read offline.access",
    "refresh_token":"ANYSTRING"
}

それでレスポンスを受け取った時に refresh_token も確実に保存しておかないと、ユーザに再度 OAuth認証を通過してもらわないといけなくなる。ここが分かっていなかったので苦戦した。

実装

Laravel での最終的な実装はこんな感じ。Socialite パッケージの Xプロバイダーを拡張している。

composer.json
    "require": {
        "php": "^8.2",
        "guzzlehttp/guzzle": "^7.2",
        "laravel/framework": "^10.10",
        "laravel/socialite": "^5.14",
        "socialiteproviders/manager": "^4.7"
    },
XProvider.php
<?php

namespace App\Socialite\Two;

use GuzzleHttp\RequestOptions;
use SocialiteProviders\Manager\ConfigTrait;

class XProvider extends \Laravel\Socialite\Two\XProvider
{
    use ConfigTrait;

    public function refreshXToken(string $refreshToken): object
    {
        $response = $this->getHttpClient()->post($this->getTokenUrl(), [
            RequestOptions::HEADERS => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/x-www-form-urlencoded',
                'Authorization' => 'Basic ' . $this->getAuthorizationBasicHeader(),
            ],
            RequestOptions::FORM_PARAMS => [
                'grant_type' => 'refresh_token',
                'refresh_token' => $refreshToken,
            ],
        ]);

        $body = json_decode($response->getBody());
        return !empty($body->data) ? $body->data : $body;
    }

    public function getAuthorizationBasicHeader(): string
    {
        return base64_encode("{$this->clientId}:{$this->clientSecret}");
    }
}

refreshXToken メソッドで得られたアクセストークンと共にリフレッシュトークンも忘れずに保存しておく。

何かのサービスクラス.php
    $response = $provider->refreshXToken($account->refresh_token);

    $account->fill([
        'token' => $response->access_token,
        'refresh_token' => $response->refresh_token,
        'expires_at' => new Carbon('+' . (string)$response->expires_in . ' seconds'),
    ]);
    $account->save();

リフレッシュトークンのあり方

そもそもリフレッシュトークンに有効期限があるべきか論については様々な議論がされている。
一度発行されたら無期限に使えたほうがいいと考えるDevも多いようだが(X開発コミュニティでもその意見が多数)、個人的にはこちらの記事にまとまっている考え方に同意する。

  • クライアント側ではリフレッシュトークンが無効化されることを前提で設計しておくのがベター
  • リフレッシュトークンがいつどのような形で無効化されるかはサービスごとに仕様が異なる

例えば Google のリフレッシュトークンは継続して使っているかぎり無期限に使えるが、6ヶ月使用されなかったリフレッシュトークンは無効になる。

同じように困っているどなたかのお役に立てばこれ幸い。

Discussion