decolua/9router · error

pollDeviceToken: missing nonce or code verifier

Error message

pollDeviceToken: missing nonce or code verifier

What it means

QoderService.pollDeviceToken polls Qoder's device-token endpoint using the nonce and PKCE code verifier issued at flow start. The method throws this terminal error immediately when either argument is missing or empty, before any network call is made. It is a programmer-error guard: the OAuth device flow cannot complete without both values.

Source

Thrown at src/lib/oauth/services/qoder.js:99

    return {
      verificationUriComplete: `${QODER_LOGIN_URL}?${params.toString()}`,
      codeVerifier: verifier,
      nonce,
      machineId,
    };
  }

  /**
   * Single poll attempt. Returns one of:
   *   { status: "pending" }       — keep polling
   *   { status: "ok", token, ... } — user authorized, tokens captured
   *   throws Error                 — terminal failure
   *
   * Upstream returns 202/404 while waiting; 200 with a JSON body when done.
   */
  async pollDeviceToken({ nonce, codeVerifier }) {
    if (!nonce || !codeVerifier) {
      throw new Error("pollDeviceToken: missing nonce or code verifier");
    }
    const url = `${QODER_DEVICE_TOKEN_URL}?nonce=${encodeURIComponent(nonce)}&verifier=${encodeURIComponent(codeVerifier)}&challenge_method=S256`;

    const response = await fetchWithTimeout(url, {
      method: "GET",
      headers: {
        Accept: "application/json",
        "User-Agent": "Go-http-client/2.0",
      },
    });

    // Pending — server has registered the device code but the user hasn't
    // finished the browser flow yet. Both 202 and 404 mean "keep polling".
    if (response.status === 202 || response.status === 404) {
      return { status: "pending" };
    }

    const text = await response.text();

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Ensure startDeviceFlow's return value (containing nonce and codeVerifier) is captured and persisted before polling.
  2. Pass fields exactly as { nonce, codeVerifier } — the method does not accept snake_case aliases.
  3. Before polling, check both values are non-empty strings and abort/restart the device flow if the state was lost.

Example fix

// before
await qoder.pollDeviceToken({ nonce: state.device_code, codeVerifier: state.verifier });
// after
await qoder.pollDeviceToken({ nonce: state.nonce, codeVerifier: state.codeVerifier });
Defensive patterns

Strategy: validation

Validate before calling

function canPoll(state) {
  return typeof state?.nonce === 'string' && state.nonce.length > 0 &&
         typeof state?.codeVerifier === 'string' && state.codeVerifier.length > 0;
}
if (!canPoll(deviceFlowState)) throw new Error('device flow state incomplete: restart flow');

Type guard

function hasDeviceFlowState(s) {
  return s !== null && typeof s === 'object' &&
    typeof s.nonce === 'string' && s.nonce.length > 0 &&
    typeof s.codeVerifier === 'string' && s.codeVerifier.length > 0;
}

Prevention

When it happens

Trigger: Calling pollDeviceToken({}) or with null/undefined/empty-string nonce or codeVerifier — e.g. when startDeviceFlow's returned state was dropped, or fields were read under wrong key names (code_verifier vs codeVerifier).

Common situations: Storing device-flow state in a DB/session and deserializing with wrong property names; passing the device code instead of the nonce; losing the verifier across a server restart so the PKCE check can never succeed.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/0d2a41da1ee124c4. Report an issue: GitHub.