Wei-Shaw/sub2api · error

auth.dingtalk.callbackMissingToken

Error message

auth.dingtalk.callbackMissingToken

What it means

In frontend/src/views/auth/DingTalkCallbackView.vue:582, finalizeCompletion() processes a PendingOAuthExchangeResponse from the DingTalk OAuth exchange. After the 'bind' branch is excluded, it requires isOAuthLoginCompletion(completion) (i.e., the response carries an access_token). If the completion is neither bind nor login — no access_token — it throws the localized 'auth.dingtalk.callbackMissingToken'. This indicates the backend exchange endpoint returned an unexpected/incomplete completion shape.

Source

Thrown at frontend/src/views/auth/DingTalkCallbackView.vue:582

    states.includes('bind_login_required') ||
    states.includes('bind_login') ||
    states.includes('adopt_existing_user_by_email') ||
    states.includes('existing_account_required') ||
    states.includes('existing_account_binding_required')
}

async function finalizeCompletion(completion: PendingOAuthExchangeResponse, redirect: string) {
  if (getOAuthCompletionKind(completion) === 'bind') {
    const bindRedirect = sanitizeRedirectPath(completion.redirect || '/profile')
    clearPendingAuthSession()
    clearAllAffiliateReferralCodes()
    appStore.showSuccess(bindSuccessMessage)
    await router.replace(bindRedirect)
    return
  }

  if (!isOAuthLoginCompletion(completion)) {
    throw new Error(t('auth.dingtalk.callbackMissingToken'))
  }

  persistOAuthTokenContext(completion)
  await authStore.setToken(completion.access_token)
  clearAllAffiliateReferralCodes()
  appStore.showSuccess(t('auth.loginSuccess'))
  await router.replace(redirect)
}

async function finalizePendingAccountResponse(completion: DingTalkPendingActionResponse) {
  applyAdoptionSuggestionState(completion)
  const redirect = sanitizeRedirectPath(completion.redirect || redirectTo.value)

  // step=email_completion: 用户无邮箱,需要跳到补邮箱页面
  if (completion.step === 'email_completion' || (completion as Record<string, unknown>)['requires_email_completion'] === true) {
    await router.replace('/auth/dingtalk/email-completion?redirect=' + encodeURIComponent(redirect))
    return
  }

View on GitHub (pinned to 073e92d171)

Solutions

  1. Capture the actual exchange response payload in devtools and confirm which field is missing; if the token lives under a different key, fix the API mapping.
  2. Make getOAuthCompletionKind/isOAuthLoginCompletion mutually exhaustive: any third shape should route to a pending-account or error flow rather than throw a raw error to the user.
  3. Handle the double-submit/refresh case by making the exchange idempotent or redirecting the user to restart login cleanly.
  4. Check backend logs for the same request to see whether it intended a pending response the frontend doesn't understand.

Example fix

// before
if (!isOAuthLoginCompletion(completion)) {
  throw new Error(t('auth.dingtalk.callbackMissingToken'))
}

// after
if (!isOAuthLoginCompletion(completion)) {
  if (isPendingAccountResponse(completion)) {
    await finalizePendingAccountResponse(completion)
    return
  }
  throw new Error(t('auth.dingtalk.callbackMissingToken'))
}
Defensive patterns

Strategy: type-guard

Type guard

function isOAuthLoginCompletion(c: PendingOAuthExchangeResponse): c is PendingOAuthExchangeResponse & { access_token: string } {
  return getOAuthCompletionKind(c) === 'login' && typeof (c as any).access_token === 'string' && (c as any).access_token.length > 0;
}

Try / catch

try { await finalizeCompletion(completion, redirect); }
catch (e) {
  if (e.message === t('auth.dingtalk.callbackMissingToken')) {
    showError('Sign-in session expired — please log in again');
    await router.replace('/login'); return;
  }
  throw e;
}

Prevention

When it happens

Trigger: POST exchange of the DingTalk code/state returns 2xx but without access_token: backend session for the callback expired server-side; the exchange response was truncated or the API version returns token under a different field; the user's account is in a pending/intermediate state (e.g., needs account creation) and the frontend treated a non-final response as final.

Common situations: User refreshes the callback URL after the one-time exchange session was consumed; backend deployed with mismatched response schema; race where pending-session handoff completes between steps and completion kind is neither 'bind' nor login.

Related errors


AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15). Data as JSON: /api/errors/9f8ca6135726d769. Report an issue: GitHub.