invalid_grant security protocol_error ai_generated true

OAuth2刷新令牌轮换失败:检测到刷新令牌重用

OAuth2 refresh token rotation failed: refresh token reuse detected

ID: security/oauth2-refresh-token-rotation-failure

其他格式: JSON · Markdown 中文 · English
85%修复率
87%置信度
1证据数
2024-04-01首次发现

版本兼容性

版本状态引入弃用备注
OAuth 2.0 RFC 6749 active
Auth0 OIDC 2.0 active
Keycloak 21.0.0 active
Microsoft Identity Platform v2.0 active

根因分析

OAuth2授权服务器检测到之前使用过的刷新令牌被重用,表明可能存在令牌窃取;服务器撤销了发给该客户端的所有令牌。

English

The OAuth2 authorization server detected that a previously used refresh token was reused, indicating potential token theft; the server revokes all tokens issued to the client in response.

generic

官方文档

https://datatracker.ietf.org/doc/html/rfc6749#section-6

解决方案

  1. Implement refresh token rotation correctly: after each successful refresh, discard the old refresh token and store the new one. Example in a Node.js OAuth2 client:
    
    async function refreshAccessToken(refreshToken) {
      const response = await fetch('https://auth.example.com/token', {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: new URLSearchParams({
          grant_type: 'refresh_token',
          refresh_token: refreshToken,
          client_id: 'your-client-id',
          client_secret: 'your-client-secret'
        })
      });
      const data = await response.json();
      if (data.refresh_token) {
        // Securely store the new refresh token
        await storeRefreshToken(data.refresh_token);
      }
      return data.access_token;
    }
  2. Use short-lived refresh tokens and implement a fallback to the authorization code flow if refresh fails. For example, in Auth0:
    
    const client = new OAuth2Client({
      clientId: 'your-client-id',
      authorizationUri: 'https://auth.example.com/authorize',
      tokenUri: 'https://auth.example.com/token',
      redirectUri: 'https://app.example.com/callback',
      usePKCE: true,
      refreshTokenRotation: true
    });
    try {
      const token = await client.refreshToken(oldRefreshToken);
    } catch (error) {
      if (error.message.includes('refresh token reuse')) {
        // Redirect user to login again
        window.location.href = '/login';
      }
    }
  3. If using Keycloak, configure refresh token revocation on reuse in the realm settings:
    
    Keycloak Admin Console -> Realm Settings -> Tokens -> 'Revoke Refresh Token' = ON
    
    This ensures that any reuse triggers immediate revocation.

无效尝试

常见但无效的做法:

  1. Ignore the error and retry the same refresh token 100% 失败

    Once the server detects reuse, it revokes all tokens; retrying the same token will always fail.

  2. Use a new refresh token without going through the full authorization flow 90% 失败

    The server expects the client to obtain a new refresh token via a fresh authorization code or client credentials flow; there is no shortcut.

  3. Store multiple refresh tokens and rotate among them 95% 失败

    The server tracks reuse per token; any reused token will trigger revocation, regardless of rotation strategy.