yikart/AiToEarn · error · Error

Failed to refresh access token

Error message

Failed to refresh access token

What it means

refreshAccessToken POSTs to https://oauth2.googleapis.com/token with client_id, client_secret, refresh_token and grant_type=refresh_token. If the Axios call or any subsequent step fails (invalid refresh token, bad client credentials, missing user record, Redis/DB errors), it throws a generic Error('Failed to refresh access token'), hiding the Google error payload. Called by getUserAccessToken, so it breaks all authenticated YouTube API calls.

Source

Thrown at project/aitoearn-electron/server/src/modules/plat/youtube/youtube.auth.service.ts:210

      );

      const TokenInfo = {
        phone: userInfo?.phone ?? '', // 如果 userInfo.phone 为 undefined 或 null,则使用空字符串
        id: userId,
        name: userInfo.name,
        isManager: false,
        googleId: userInfo?.googleAccount?.googleId ?? ''
      }
      console.log("发送获取systemToken的info---", TokenInfo);
      const systemToken = await this.AuthService.generateToken(TokenInfo)

      const returnRes = {url: systemToken};
      return returnRes;
      // 返回新的 access token 和其他信息
      // return response.data;  // 包含新的 access_token、expires_in、token_type 等信息
    } catch (err) {
      console.log('Error while refreshing access token', err);
      throw new Error('Failed to refresh access token');
    }
  }

  /**
   * 验证并保存授权码
   * @param code 授权码
   * @param state 状态码
   * @returns 系统令牌
   */
  async handleAuthorizationCode(code: string, state: string, userId: string) {
    try {
      // 获取state关联的邮箱信息
      const stateInfo = await this.redisService.get(`youtube:state:${userId}:${state}`);
      if (!stateInfo || !stateInfo.mail) {
        throw new Error('无效的状态码');
      }

      // 使用授权码获取访问令牌和刷新令牌

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Log error.response?.data from the Axios failure to see Google's exact reason (invalid_grant vs invalid_client).
  2. Re-authorize the YouTube account with prompt=consent to obtain a fresh refresh_token when invalid_grant occurs.
  3. Verify GOOGLE_CONFIG.WEB_CLIENT_ID and WEB_CLIENT_SECRET match the OAuth client used for authorization.
  4. Fix getUserAccessToken to handle a null accountTokenInfo before calling refreshAccessToken.
  5. Publish the Google OAuth app (or keep tokens fresh) to avoid 7-day testing-mode token expiry.

Example fix

// before
} catch (err) {
  console.log('Error while refreshing access token', err);
  throw new Error('Failed to refresh access token');
}
// after
} catch (err) {
  const reason = err?.response?.data?.error || err.message
  console.log('Error while refreshing access token:', reason)
  throw new Error(`Failed to refresh access token: ${reason}`)
}
Defensive patterns

Strategy: retry

Validate before calling

const token = await AccountTokenModel.findOne({ accountId })
if (!token?.refreshToken) throw new Error('no refresh token stored; user must re-authorize with prompt=consent')

Type guard

const isInvalidGrant = (e: unknown): boolean =>
  (e as any)?.response?.data?.error === 'invalid_grant' ||
  /invalid_grant|invalid_client/i.test(String((e as any)?.message))

Try / catch

try {
  await youtubeAuthService.refreshAccessToken(userId, accountId, refreshToken)
} catch (e) {
  if (isInvalidGrant(e)) {
    await markAccountNeedsReauth(accountId) // surface 're-connect account' to the user
    return
  }
  await sleep(2000) // transient network error
  return retryOnce()
}

Prevention

When it happens

Trigger: The stored refresh_token is revoked or expired (Google returns 400 invalid_grant), GOOGLE_CONFIG.WEB_CLIENT_ID/SECRET are wrong (401 invalid_client), refreshToken is undefined because the account never consented with prompt=consent, or accountTokenInfo lookup in getUserAccessToken returned null causing a TypeError.

Common situations: Google revokes refresh tokens after 6 months of inactivity or when the user changes password; app in 'Testing' mode where tokens expire after 7 days; rotating client secrets in Google Cloud Console without updating env; re-authorizing without prompt=consent so no refresh_token is issued.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/489c5dd8ebe6f699. Report an issue: GitHub.