yikart/AiToEarn · error · AppException
ChannelAuthCsrfInvalid
ChannelAuthCsrfInvalid
Error message
ResponseCode.ChannelAuthCsrfInvalid
What it means
Thrown in parseMiniAppCallback when the zod schema validation of the Douyin mini-app callback payload fails specifically on the 'state' field. 'state' carries the CSRF/session identifier, so an invalid state means the callback cannot be tied to a legitimate auth session.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-auth.provider.ts:238
private parseMiniAppCallback(input: AuthCallbackInput): DouyinMiniAppCallback | undefined {
const payload = {
state: input.query?.state ?? input.body?.state,
token: input.query?.token ?? input.body?.token,
nickname: input.query?.nickname ?? input.body?.nickname,
avatar: input.query?.avatar ?? input.body?.avatar,
tickets: input.body?.tickets,
}
if (payload.token === undefined && payload.tickets === undefined) {
return undefined
}
const result = DouyinMiniAppCallbackSchema.safeParse(payload)
if (!result.success) {
const hasInvalidState = result.error.issues.some(issue => issue.path[0] === 'state')
const hasInvalidToken = result.error.issues.some(issue => issue.path[0] === 'token')
if (hasInvalidState) {
throw new AppException(ResponseCode.ChannelAuthCsrfInvalid)
}
if (hasInvalidToken) {
throw new AppException(ResponseCode.ChannelAuthCodeMissing)
}
throw new AppException(ResponseCode.ChannelAuthorizationFailed)
}
assertParsedCallbackState(result.data.state, input.session.id)
return result.data
}
private hasMiniAppScope(scopes: string | undefined, scope: string) {
return !!scopes?.split(/[,\s]+/).includes(scope)
}
private maskOpenId(openId: string) {
if (openId.length <= 8) {View on GitHub (pinned to d3aa8bea5b)
Solutions
- Re-initiate the mini-app authorization flow to generate a fresh state bound to the current session
- Check that the frontend forwards the full callback query (including state) unchanged to the backend
- Verify DouyinMiniAppCallbackSchema's state field still matches what the Douyin flow actually sends
Defensive patterns
Strategy: validation
Validate before calling
const state = callbackPayload?.state
if (typeof state !== 'string' || state.length === 0) {
throw new Error('Missing or invalid OAuth state parameter')
} Type guard
function hasValidState(payload: unknown): payload is { state: string } {
return typeof payload === 'object' && payload !== null && typeof (payload as any).state === 'string' && (payload as any).state.length > 0
} Try / catch
try {
await provider.miniAppCallback(payload)
} catch (e) {
if (e instanceof AppException && e.code === 'ChannelAuthCsrfInvalid') {
return { restartAuth: true, message: 'Authorization session expired or invalid, please retry' }
}
throw e
} Prevention
- Store state server-side keyed to the session and verify it before forwarding the callback
- Avoid bookmarking/refreshing OAuth callback URLs — always start a new flow
- Ensure the frontend forwards the callback query string intact
- Expire auth sessions promptly so stale states fail cleanly with a retry hint
When it happens
Trigger: Douyin mini-app callback POST whose state is absent, malformed, or fails assertParsedCallbackState-shaped schema checks (e.g. tampered query params, callback replayed from a different session).
Common situations: User bookmarked/refreshed the callback URL, session expired and state no longer parses, a bot hits the callback endpoint with junk parameters, or payload structure changed after a Douyin SDK/flow update.
Related errors
- ChannelAuthCodeMissing
- ChannelAuthRefreshTokenMissing
- ChannelAuthPlatformUidMissing
- ChannelAccessTokenFailed
- ChannelAuthorizationFailed
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/f4f919babf2049aa.
Report an issue: GitHub.