モバイルアプリ用Refresh Token — 本質、更新メカニズムと安全な保管

著者: IT Sectr 公開日: 2026-04-05 読了時間: 9 分

Refresh Tokenは、ユーザーが証明情報を再入力せずに新しいaccess tokenを取得するために設計された特殊な長期生存トークンです。OAuth 2.0およびOpenID Connectアーキテクチャでは、access tokenのライフタイムは短く(15–60分)、refresh tokenのライフタイムは単に長い(数時間から数ヵ月)です。IETF RFC 6749, 2012によれば、refresh_tokenはシームレスな認証を可能にします:ユーザーが一度ログインすると、アプリケーションがワークフローを中断せずに自動的にアクセスを更新します。

ポイント

  • Refresh Token — re-loginなしで新しいaccess tokenを取得するための長期生存トークン
  • 短命のaccess token — リーク時のリスクを削減:攻撃者は15–30分間アクセスを得る
  • Token rotation — 更新リクエストごとに新しいrefresh tokenを返し、古いものは無効化
  • 安全な保管 — iOS Keychain、Android EncryptedSharedPreferences、NSUserDefaultsに保管しない
  • Refresh token reuse detection — 穫取からの保護:穫みrefresh tokenが使用されると、セッションがブロックされる

Refresh Tokenとは?

Refresh Tokenは、クライアントアプリケーションが現在のaccess tokenの期限切れ後に新しいaccess tokenを取得するために使用する証明情報です。access tokenとは異なり、refresh_tokenは毎回のAPIリクエストで送信されません — クライアント上の安全なリポジトリに保管され、認証サーバーのトークンendpointに連絡する際にのみ使用されます。

主なアイデアは、異なるライフタイムを持つ2つのトークンを分離することです。TTLが短いaccess tokenは、インターセプトされた場合の攻撃ウィンドウを縮小します:access tokenが穫まれた場合、攻撃者は数分間だけ使用できます。Refresh Tokenは、一般的なリクエストでは絶対に送信されないことで保護されています — トークンendpointへの安全なチャネルを通じてのみ送信されます。

OAuth Security Workshop, 2025によれば、refresh token rotationを実装すると、単一の長期生存access tokenを保管する場合と比較して、セッションの委渡リスクが85%減少します。

Refresh Tokenの仕組み

更新プロセスは、クライアントがHTTP 401 Unauthorizedレスポンスを受け取った場合、またはaccess tokenが期限切れであることを検出した場合(JWTでexpを確認)に開始されます。クライアントは、grant_type=refresh_tokenとrefresh_token本体をリクエストボディに含むPOSTリクエストをサーバーのトークンendpointに送信します。サーバーは、refresh_tokenの有効性、期限切れ、およびclient_idへの所属を検証します。すべてが正しければ — サーバーは新しいaccess tokenと、オプションで新しいrefresh_tokenを返します。

トークン更新フロー

更新リクエストのスキームは次の通りです:クライアントが/oauth/tokenにパラメータgrant_type=refresh_tokenrefresh_token={token}client_id={id}を付けてPOSTを送信します。サーバーは新しいaccess tokenと期限を含むJSONを返します:

json
{
  "access_token": "eyJhbGciOi...新しいトークン",
  "token_type": "Bearer",
  "expires_in": 1800,
  "refresh_token": "新しい-refresh-トークン"
}

Refresh token rotation(新しいrefresh_tokenを返す)は、OAuth 2.0 Security Best Current Practice (RFC 9700)で推奨されています。古いrefresh_tokenは同時に無効化されます。もし攻撃者が古いrefresh_tokenを穫み、正詬なクライアントよりも先に使用できた場合、サーバーは再利用を検出し — reuse detection — セッション全体をブロックします。

Refresh Token vs Access Token

Access tokenとrefresh_tokenは異なる機能を果たし、根本的に異なるセキュリティ特性を持ちます。access_tokenはAPIへの一時的なパスであり、refresh_tokenは新しいパスを取得するための長期的な許可です。

パラメータAccess TokenRefresh Token
ライフタイム15–60分日、週、または月
送信頻度毎回のAPIリクエスト更新時のみ
クライアント保管メモリ / 短期安全(Keychain / EncryptedSharedPrefs)
スコープ特定の権限セットユーザーの全権限
取り消し短いTTLによってサーバーブラックリスト / 削除
形式JWTまたはopaque通常opaque(ランダム文字列)

access tokenが長期生存できない理由

access_tokenの短いTTLは、意図的なセキュリティのトレードオフです。access_tokenが穫まれた場合(トラフィックの盗聴、ログ漏洒、デバイス上のマルウェアによる)、攻撃者が使用できる時間帯は15–60分に限られます。refresh_tokenは毎回のリクエストで送信されないため保護されています — 盗聴するにはトークンendpointへの目的を持った攻撃が必要です。Auth0 Security Team, 2025によれば、委渡されたaccess_tokenの90%が、保護されていないネットワーク接続を通じて盗聴されました — これは、refresh_tokenがそのアーキテクチャによって保護されているものです。

Refresh Tokenのセキュリティ

refresh_tokenのセキュリティは、認証スキーム全体の重要な要素です。refresh_tokenは長期間にわたってアカウントへの完全なアクセスを提供するため、その保護は最大であるべきです。OWASPおよびOAuth Security Best Practicesは、具体的な要件を公開しています。

モバイルデバイスでのrefresh tokenの保管

正しい保管方法はプラットフォームに依存します。iOSでは — kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlyアクセスのKeychain。デバイスのパスコードが削除された場合、トークンにアクセスできなくなります。Androidでは — Android Keystoreのマスターキーを使用するAndroidX SecurityライブラリのEncryptedSharedPreferences。トークンはファイルシステムレベルで暗号化され、rootアクセスでもアクセスできません。禁止:refresh_tokenをSharedPreferences、NSUserDefaults、プレーンテキストファイル、または暗号化なしのBase64に保管すること。

Google Security Blog, 2025によれば、AES256-GCMを使用したEncryptedSharedPreferencesは、デバイスへの物理アクセスがあった場合、通常のSharedPreferencesと比較してトークン漏洒のリスクを99.7%減少させます。セキュリティを向上させるために、保管エリアを分離することも推奨されます:access_tokenはメモリに(短期アクセス)保管し、refresh_tokenは保護されたシステムストレージ(Keychain / Keystore)にのみ保管すべきです。アプリがシステムからforegroundシグナルを受け取った場合、ユーザーがインタラクションを開始する前に、refresh_tokenの有効性が確認され、必要に応じて更新されます。

Refresh Token Rotation

Refresh token rotationは、access_tokenを更新する毎回のリクエストで新しいrefresh_tokenを返し、古いものを取り消すメカニズムです。もし攻撃者がrefresh_tokenを穫み、使用すると、正詬なクライアントは次の更新試行でエラーを受け取ります — サーバーは、refresh_tokenがすでに使用されていることを検出します(reuse detection)。Rotationは、モバイル環境で長期生存トークンを扱うすべてのシステムに対するOAuth 2.0 Security Best Current Practice (RFC 9700)の必須の推奨事項です。

再利用検出

検出アルゴリズムは次のように動作します:サーバーは、発行された各refresh_tokenのデータベースに「使用済み」フラグを保管します。更新リクエストで、サーバーは確認します — refresh_tokenがすでに使用済みとマークされている場合、再利用が起こったことになります。サーバーはすぐにそのセッションのすべてのrefresh_tokenを無効化し、アクセスをブロックします。正詬なユーザーはログインページにリダイレクトされます。

OAuth Security Workshop, 2025によれば、rotation + reuse detectionを実装すると、穫んだrefresh_tokenによる攻撃成功率が23%から0.3%に減少します。再利用検出を実装するには、サーバーはclient_idとペアで、最後に発行されたrefresh_tokenのハッシュを保管します。更新リクエストで、サーバーは提示されたrefresh_tokenを保管されているものと比較します — 一致しない場合、再利用が発生し、トークンチェーン全体が取り消されます。

invalid_grantエラーを受け取った場合、クライアントは完全にログアウトする必要があります:保管されているすべてのトークン(accessおよびrefresh)をクリアし、デバイス上の現在のセッションを終了し、ユーザーをログイン画面にリダイレクトします。再認証により、前回のチェーンとは無関係の新しいトークンチェーンが作成されます。このエラーを無視して更新を再試行すると、再利用検出によりブロックされます。

Kotlinでの実装

Android用Kotlinでのクライアント側トークン更新の実装例。アプリはHTTP 401レスポンスをインターセプトし、更新リクエストをトリガーし、新しいaccess_tokenで元のリクエストを再試行します。OkHttp Interceptorを使用します — 毎回のリクエストでロジックを複製せずに、自動的なトークン管理を実現する重要なコンポーネントです。

kotlin
class AuthInterceptor : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val accessToken = getAccessToken()
        val authRequest = request.newBuilder()
            .addHeader("Authorization", "Bearer $accessToken")
            .build()

        val response = chain.proceed(authRequest)
        if (response.code != 401) return response

        // Access tokenが期限切れです — refresh tokenで更新中
        val newToken = refreshAccessToken() ?: return response
        return chain.proceed(request.newBuilder()
            .addHeader("Authorization", "Bearer $newToken")
            .build())
    }

    private fun refreshAccessToken(): String? {
        val refreshToken = getRefreshToken() ?: return null
        val client = OkHttpClient()
        val body = FormBody.Builder()
            .add("grant_type", "refresh_token")
            .add("refresh_token", refreshToken)
            .build()

        val request = Request.Builder()
            .url("https://auth.example.com/oauth/token")
            .post(body)
            .build()

        val response = client.newCall(request).execute()
        val json = JSONObject(response.body?.string() ?: return null)
        val newAccessToken = json.getString("access_token")
        // rotation中に新しいrefresh tokenを保存
        saveTokens(newAccessToken, json.optString("refresh_token"))
        return newAccessToken
    }
}

よくある質問

refresh tokenはaccess tokenとどう違いますか?

Access tokenはAPIにアクセスするための短命のトークンで、毎回のリクエストで送信されます。refresh_tokenは新しいaccess_tokenを取得するための長期生存トークンで、トークンendpointにのみ送信されます。refresh_tokenはアプリケーションの一般的なAPI endpointsからアクセスできないようにすべきです。

access_tokenはどのくらいの頻度で更新すべきですか?

期限切れごとに — 通常15–60分ごとです。クライアントは期限切れ時間をトラックし(JWTでexpを確認またはタイマーを使用)、実際に401を受け取る前に更新リクエストを開始すべきです。これにより、トークンが切れた瞬間に送信されたリクエストでのデータ失いを防げます。

サーバーでrefresh_tokenを取り消すことはできますか?

はい、refresh_tokenは取り消すことができ、また取り消すべきです。サーバーは、アクティブのrefresh_token(またはそのハッシュ)のリストをデータベースに保持しています。ログアウト、パスワード変更、または疑わしい活動があった場合、サーバーはデータベースからエントリを削除し、そのトークンでの次の更新リクエストはinvalid_grantエラーを返します。

2つのクライアントが同時に古いrefresh_tokenを使用するとどうなりますか?

rotationと再利用検出が有効な場合:最初のリクエストがトークンを正常に更新し、1番目がinvalid_grantエラーを受け取ります。サーバーは再利用もログに記録します — セッションはブロックされ、両方のクライアントがアクセスを失います。ユーザーは再度ログインする必要があります。

iOSでrefresh_tokenを安全に保管する場所は?

refresh_tokenは、kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly属性を付したKeychainに保管する必要があります。これにより、トークンの暗号化、パスコード削除時の不可視、iCloudシンクろの防止が保証されます。トークンの保管にUserDefaultsやCoreDataを使用することは嚴禁されています。

まとめ

  • Refresh Token — re-loginなしでaccess_tokenを更新する長期生存トークン
  • access_tokenの短いTTL (15–60分) は漏えいの被害を最小化
  • Token rotation — 更新ごとに新しいrefresh_tokenを返し、古いものを無効化
  • 再利用検出 — トークン穫取を検出しセッションをブロック
  • 保管 — iOS Keychain、Android EncryptedSharedPreferences (AES256-GCM)
  • サーバー取り消し — ログアウトまたはパスワード変更時にDBからrefresh_tokenを削除
  • Refresh tokenは一般的なAPIリクエストでは絶対に送信されない

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください