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

OpenAppService.verifySignInCode consumes a one-time challenge of type 'open_app_sign_in' (created with a 5-minute TTL) and throws InvalidAuthState (invalid_auth_state) when consumption returns no payload or the payload lacks a userId - i.e. the code from the web 'open in app' flow is unknown, expired, or already used.

Solutions

  1. Generate a fresh code in the web app and resubmit immediately (within 5 minutes)
  2. Never retry the same code after a failure - always start a new challenge
  3. Copy the full code without truncation
  4. Check the device clock if freshly generated codes still fail
Defensive patterns

Strategy: try-catch

Validate before calling

const OPEN_APP_CODE_TTL_MS = 5 * 60 * 1000;
function isCodeStillValid(issuedAt: number): boolean {
  return Date.now() - issuedAt < OPEN_APP_CODE_TTL_MS;
}

Try / catch

try {
  await verifySignInCode(code);
} catch (e) {
  if (isAffineErrorCode(e, 'invalid_auth_state')) {
    const fresh = await generateNewOpenAppCode(); // codes are single-use, 5-minute TTL
    await verifySignInCode(fresh);
  } else throw e;
}

Prevention

When it happens

Trigger: Entering/scanning an open-app code more than 5 minutes after it was generated; re-submitting a code that already succeeded (challenges are single-use); a mistyped or truncated code; the challenge record expiring server-side before submission.

Common situations: Users letting the QR/code screen sit before scanning; retries after network failures double-submitting the same code; copy/paste losing characters; clock skew between issuing and consuming services.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/d9fe744f3cfc7808. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/auth/open-app.ts:27

export class OpenAppAuthService {
  constructor(private readonly challenges: AuthChallengeStore) {}

  async createSignInCode(user: CurrentUser) {
    return this.challenges.create(
      'open_app_sign_in',
      { userId: user.id },
      5 * 60 * 1000
    );
  }

  async verifySignInCode(code: string): Promise<VerifiedIdentity> {
    const payload = await this.challenges.consume<{ userId?: string }>(
      'open_app_sign_in',
      code
    );

    if (!payload?.userId) {
      throw new InvalidAuthState();
    }

    return { userId: payload.userId, method: 'open_app' };
  }
}

View on GitHub (pinned to b4c8548c09)