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
- Increase the Redis TTL set in getAuthorizationUrl if users take long on the consent screen
- Ensure all app instances share the same Redis instance so state written by one pod is readable by another
- 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
- Do not delete the state before successful token exchange, or handle replay idempotently
- 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
- Set a generous state TTL (e.g. 10 min) and surface a 'restart authorization' UX path
- Share one Redis across all app instances
- Never reuse callback URLs — state is single-use and deleted after consumption
- Ensure state is URL-safe (base64url) to avoid encoding mismatches
- Alert on tiktok:state Redis eviction under memory pressure
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
- 状态数据不完整
- No response from Gemini
- ResponseCode.ChannelAuthSessionInvalid
- ChannelAuthCsrfInvalid
- ChannelAuthRefreshTokenMissing
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/3aac4ec8d31599f4.
Report an issue: GitHub.