toeverything/AFFiNE · error · InvalidAuthState
invalid_auth_state
invalid_auth_state
Error message
Invalid auth state. You might start the auth progress from another device.
What it means
Thrown by `POST /api/auth/open-app/sign-in` when the body fails `OpenAppSignInBodySchema.safeParse`. The schema is `{ code: z.string().min(1).max(512) }.strict()`, so a missing/empty/too-long `code`, an extra field, or a non-object body all fail. HTTP 400 (presented as invalid_auth_state because the one-time code is the auth state).
Source
Thrown at packages/backend/server/src/core/auth/controller.ts:232
@Public()
@UseNamedGuard('version')
@Post('/open-app/sign-in-code')
async openAppSignInCode(@CurrentUser() user?: CurrentUser) {
if (!user) throw new ActionForbidden();
const code = await this.openApp.createSignInCode(user);
return { code };
}
@Public()
@UseNamedGuard('version')
@Post('/open-app/sign-in')
async openAppSignIn(
@Req() req: Request,
@Res() res: Response,
@Body() body?: unknown
) {
const credential = OpenAppSignInBodySchema.safeParse(body);
if (!credential.success) throw new InvalidAuthState();
const identity = await this.openApp.verifySignInCode(credential.data.code);
const { exchangeCode } = await this.sessionIssuer.issue(req, res, identity);
res.send({ id: identity.userId, exchangeCode });
}
@Public()
@UseNamedGuard('version')
@Post('/session/exchange')
@Header('Cache-Control', 'no-store')
@Header('Pragma', 'no-cache')
async exchangeSession(@Req() req: Request, @Body() body?: unknown) {
const input = AuthSessionExchangeBodySchema.parse(body);
return await this.sessionExchange.exchange(req, input.code, {
installationId: input.installationId,
platform: input.platform,
deviceName: input.deviceName,
appVersion: getClientVersionFromRequest(req) ?? undefined,
});View on GitHub (pinned to 26c515e050)
Solutions
- Send exactly `{ code: string }` with a 1–512 char code obtained from `/open-app/sign-in-code`.
- Validate the code is non-empty on the client before posting.
- Regenerate the code if it was already consumed or expired (codes live 60s).
Example fix
// before
fetch('/api/auth/open-app/sign-in', { method: 'POST', body: '{}' });
// after
fetch('/api/auth/open-app/sign-in', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ code }),
}); Defensive patterns
Strategy: validation
Validate before calling
import { z } from 'zod';
const Body = z.object({ code: z.string().min(1).max(512) }).strict();
const parsed = Body.safeParse(payload);
if (!parsed.success) throw new Error('code required (1-512 chars)');
await fetch('/api/auth/open-app/sign-in', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(parsed.data),
}); Type guard
function isOpenAppSignInBody(v: unknown): v is { code: string } {
return typeof v === 'object' && v !== null &&
typeof (v as any).code === 'string' &&
(v as any).code.length >= 1 && (v as any).code.length <= 512;
} Prevention
- Validate the code is non-empty and ≤512 chars before posting.
- Regenerate the code if older than 60 seconds (challenge TTL).
- Match the strict schema — send only `{ code }`.
When it happens
Trigger: Posting `{}` or `{ code: '' }`, omitting the code from the desktop handoff, replaying a code format the strict schema rejects, or attaching extra fields.
Common situations: Desktop client sends an empty code before the browser generated one, the code query param was lost in the deep-link handoff, or a client version sends a different payload shape.
Related errors
- invalid_email
- email_token_not_found
- Failed to read image size
- ErrorCode.DefaultRuntimeError
- Invalid key for: ${key}
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/94c31c57f9207602.
Report an issue: GitHub.