yikart/AiToEarn · warning · BadRequestException

无效的状态参数或状态已过期

Error message

无效的状态参数或状态已过期

What it means

handleAuthorizationCallback validates TikTok's OAuth callback `state` parameter against data previously stored in Redis under `tiktok:state:<state>`. If Redis has no entry for the state, it throws BadRequestException('无效的状态参数或状态已过期') ('invalid state parameter or state expired'), protecting against CSRF and replayed/expired callbacks.

Source

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

   * @param state 状态码
   * @returns 处理结果
   */
  async handleAuthorizationCallback(code: string, state: string): Promise<object> {
    // // 解析状态参数
    // let parsedState;
    // try {
    //   parsedState = JSON.parse(decodeURIComponent(state));
    // } catch (error) {
    //   this.logger.error('无法解析状态参数:', error);
    //   throw new BadRequestException('无效的状态参数格式');
    // }

    // 从Redis获取保存的状态信息
    // const originalState = parsedState.state;

    const stateDataJson = await this.redisService.get(`tiktok:state:${state}`);
    if (!stateDataJson) {
      throw new BadRequestException('无效的状态参数或状态已过期');
    }
    // 解析状态数据
    const stateData = JSON.parse(stateDataJson);
    console.log('stateData:------', stateData);
    const { userId, codeVerifier } = stateData;
    if (!userId || !codeVerifier) {
      throw new BadRequestException('状态数据不完整');
    }

    // 删除Redis中的状态信息
    await this.redisService.del(`tiktok:state:${state}`);

    try {
      // 使用授权码交换令牌,并传入codeVerifier
      const tokenResponse = await this.exchangeCodeForTokens(code, codeVerifier);
      console.log("获取授权码成功!", tokenResponse);
      // 获取用户信息
      const userProfile = await this.getTikTokUserProfile(tokenResponse.access_token, tokenResponse.open_id);

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Increase the Redis TTL set in getAuthorizationUrl if users take long on the consent screen
  2. Ensure all app instances share the same Redis instance so state written by one pod is readable by another
  3. Treat this as an expected user flow: catch it in the controller and redirect the user to restart authorization instead of returning a raw 400
  4. Do not delete the state before successful token exchange, or handle replay idempotently
  5. Check for URL-encoding differences (state passed through query may be encoded) before lookup

Example fix

// before
const stateDataJson = await this.redisService.get(`tiktok:state:${state}`);
if (!stateDataJson) {
  throw new BadRequestException('无效的状态参数或状态已过期');
}
// after
const stateDataJson = await this.redisService.get(`tiktok:state:${state}`);
if (!stateDataJson) {
  throw new BadRequestException(
    'Authorization session expired or invalid, please restart the TikTok connection process',
  );
}
Defensive patterns

Strategy: try-catch

Validate before calling

// before redirecting to TikTok, confirm the state was issued recently
const issued = await redis.ttl(`tiktok:state:${state}`);
if (issued < 0) throw new Error('State missing in Redis — restart authorization');

Try / catch

try {
  return await tiktokAuthService.handleAuthorizationCallback(state, code);
} catch (err) {
  if (err instanceof BadRequestException && err.message.includes('状态')) {
    return res.redirect('/connect/tiktok?reason=state_expired');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling handleAuthorizationCallback with a state that was never generated by getAuthorizationUrl, was already consumed and deleted from Redis, or whose Redis TTL expired before the user completed TikTok authorization.

Common situations: User left the TikTok consent screen open past the state TTL and then submitted; user reloads/replays the callback URL (state already deleted after first use); server restart or Redis eviction/flush losing the key; multi-instance deployment without shared Redis; state mangled by URL encoding issues.

Related errors


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