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
- Log error.response?.data from the Axios failure to see Google's exact reason (invalid_grant vs invalid_client).
- Re-authorize the YouTube account with prompt=consent to obtain a fresh refresh_token when invalid_grant occurs.
- Verify GOOGLE_CONFIG.WEB_CLIENT_ID and WEB_CLIENT_SECRET match the OAuth client used for authorization.
- Fix getUserAccessToken to handle a null accountTokenInfo before calling refreshAccessToken.
- 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
- Always request prompt=consent&access_type=offline so a refresh_token is issued
- Mark accounts as needing re-authorization instead of retrying forever on invalid_grant
- Keep GOOGLE_CONFIG client id/secret in sync with the OAuth client used for consent
- Publish the Google OAuth app to avoid 7-day testing-mode token expiry
- Never call refreshAccessToken without checking the token record exists
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
- Failed to refresh access token
- Invalid Google token
- Google login failed: ${error.message}
- Failed to get user permissions
- Failed to fetch user info
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/489c5dd8ebe6f699.
Report an issue: GitHub.