yikart/AiToEarn · error · Error
无效的状态码
Error message
无效的状态码
What it means
handleAuthorizationCode first validates the OAuth state by reading the Redis key youtube:state:<userId>:<state> populated in getAuthorizationUrl. If the key is missing or has no mail field, it throws Error('无效的状态码') before exchanging the authorization code.
Source
Thrown at project/aitoearn-electron/server/src/modules/plat/youtube/youtube.auth.service.ts:225
// 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('无效的状态码');
}
// 使用授权码获取访问令牌和刷新令牌
const params = new URLSearchParams({
code: code,
redirect_uri: `${this.webRenderBaseUrl}/api/plat/youtube/auth/callback`,
client_id: this.webClientId,
grant_type: "authorization_code",
client_secret: this.webClientSecret,
});
const response = await axios.post('https://oauth2.googleapis.com/token', params.toString(), {
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});
const { access_token, refresh_token, expires_in, id_token } = response.data;
// 验证ID令牌以获取用户信息View on GitHub (pinned to d3aa8bea5b)
Solutions
- Increase the state TTL in getAuthorizationUrl (currently 60*10 seconds) or regenerate state on retry.
- Ensure the callback passes the SAME userId used when generating the authorization URL.
- Verify Redis persistence (appendonly/AOF) or accept restarts will invalidate pending OAuth flows.
- Have users restart the authorization flow when this error occurs; the code exchange cannot proceed without the state.
- Return a clear 'state expired, please re-authorize' response to the frontend instead of a generic error.
Example fix
// before
this.redisService.setKey(`youtube:state:${userId}:${state}`, { mail }, 60 * 10);
// after
this.redisService.setKey(`youtube:state:${userId}:${state}`, { mail }, 60 * 30); // 30 min TTL Defensive patterns
Strategy: validation
Validate before calling
const stateInfo = await redisService.get(`youtube:state:${userId}:${state}`)
if (!stateInfo?.mail) {
// fail fast with an actionable message before hitting Google
return { code: 410, msg: '授权状态已过期,请重新发起授权' }
} Type guard
const hasValidState = (s: unknown): s is { mail: string } =>
typeof s === 'object' && s !== null && typeof (s as any).mail === 'string' && (s as any).mail.length > 0 Try / catch
try {
return await youtubeAuthService.handleAuthorizationCode(code, state, userId)
} catch (e) {
if (e.message === '无效的状态码') {
// state expired or userId mismatch; redirect user to restart OAuth, never retry the code
return redirect('/youtube/reauthorize')
}
throw e
} Prevention
- Raise the state TTL above the expected consent-screen dwell time
- Pass the originating userId end-to-end through the OAuth round trip
- Enable Redis AOF/RDB persistence so restarts don't kill pending flows
- Detect and ignore duplicate callback deliveries (second tab, page refresh)
- Encode state in the redirect rather than relying on server session identity
When it happens
Trigger: The state was consumed/expired (Redis key TTL is 60*10 seconds = 10 minutes), the callback passes a userId different from the one that started authorization, state was altered/tampered in the redirect, Redis was flushed or restarted without persistence, or getAuthorizationUrl failed to store the key.
Common situations: User waits on the Google consent screen longer than 10 minutes then approves; user re-initiates OAuth in a second tab, overwriting/expiring the first state; callback routes to a different user session than the one that generated the URL; Redis restarts losing non-persisted keys.
Related errors
- ResponseCode.ChannelAuthSessionInvalid
- ResponseCode.ChannelAuthPlatformMismatch
- ResponseCode.ChannelAuthSessionCompleted
- ResponseCode.ChannelAuthSelectableAccountsNotFound
- 无效的状态参数或状态已过期
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/87e2797d9e55efc2.
Report an issue: GitHub.