toeverything/AFFiNE · error · MissingOauthQueryParameter

missing_oauth_query_parameter

missing_oauth_query_parameter

Error message

Missing query parameter `provider`.

What it means

MissingOauthQueryParameter({name:'provider'}) thrown at packages/backend/server/src/plugins/oauth/service.ts:119 when the server-side OAuthState record loaded from the challenge store has no provider field. Normally preflight always persists provider alongside client/clientNonce/pkce, so this indicates a corrupted, legacy, or hand-crafted state record rather than a client input problem.

Source

Thrown at packages/backend/server/src/plugins/oauth/service.ts:119

    if (!state.token) state.token = stateStr;

    if (
      state.provider === OAuthProviderName.Apple &&
      rawState &&
      state.client &&
      state.client !== 'web'
    ) {
      return {
        type: 'handoff',
        code: input.code,
        provider: rawState.provider,
        state,
        stateToken: stateStr,
      };
    }

    if (!state.provider) {
      throw new MissingOauthQueryParameter({ name: 'provider' });
    }

    const provider = this.providerFactory.get(state.provider);

    if (!provider) {
      throw new UnknownOauthProvider({ name: state.provider ?? 'unknown' });
    }

    if (
      state.provider !== OAuthProviderName.Apple &&
      (!input.clientNonce ||
        !state.clientNonce ||
        state.clientNonce !== input.clientNonce)
    ) {
      throw new InvalidAuthState();
    }

    return {

View on GitHub (pinned to b4c8548c09)

Solutions

  1. Start a new login flow (new preflight writes a complete state record).
  2. If it persists, flush the stale oauth_state entries from Redis/cache after upgrading.
  3. Audit custom code that calls saveOAuthState() — it must include provider: OAuthProviderName.
  4. Check that all replicas run the same backend version so state payloads have a consistent shape.

Example fix

// before (custom code)
await this.oauth.saveOAuthState({ client: 'web', redirectUri: '/' } as OAuthState);

// after
await this.oauth.saveOAuthState({ provider: OAuthProviderName.OIDC, client: 'web', redirectUri: '/' });
Defensive patterns

Strategy: try-catch

Type guard

function isCompleteState(state: OAuthState | null): state is OAuthState {
  return !!state && typeof state.provider === 'string' && state.provider.length > 0;
}

Try / catch

try {
  await oauth.verifyCallback(input);
} catch (err) {
  if (err instanceof MissingOauthQueryParameter && err.data?.name === 'provider') {
    // corrupted/legacy state record: flush stale oauth_state cache entries, restart login
  }
}

Prevention

When it happens

Trigger: The oauth_state cache entry was written without a provider (custom code calling saveOAuthState with an incomplete object); state records created by an older server version being replayed after upgrade; direct calls to OAuthService.verifyCallback with a stateStr whose stored payload lacks provider.

Common situations: Version upgrade where the state payload schema changed and old cached states are still in Redis; a plugin or fork writing states directly into the challenge store; cache key collisions overwriting an oauth_state entry with an incomplete object.

Related errors


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