Wei-Shaw/sub2api · error

auth.linuxdo.callbackMissingToken

Error message

auth.linuxdo.callbackMissingToken

What it means

In frontend/src/views/auth/LinuxDoCallbackView.vue:583, finalizeCompletion() handles the LinuxDo OAuth exchange completion. After the bind branch, it requires isOAuthLoginCompletion(completion) — a response with access_token. Any other shape throws the localized 'auth.linuxdo.callbackMissingToken'. This mirrors the DingTalk callback: the backend exchange returned a completion that is neither bind nor login.

Source

Thrown at frontend/src/views/auth/LinuxDoCallbackView.vue:583

    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.linuxdo.callbackMissingToken'))
  }

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

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

  if (completion.error === 'invitation_required') {
    pendingAccountAction.value = 'none'
    needsInvitation.value = true
    needsAdoptionConfirmation.value = false
    isProcessing.value = false

View on GitHub (pinned to 073e92d171)

Solutions

  1. Inspect the exchange response payload in the network tab to see exactly what the backend returned.
  2. Verify the OAuth session cookie survives the redirect (SameSite settings, cross-site context).
  3. Route any non-bind/non-login completion to a friendly restart-login flow instead of throwing raw.
  4. Align frontend/backend versions to eliminate schema drift.

Example fix

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

// after
if (!isOAuthLoginCompletion(completion)) {
  appStore.showError(t('auth.linuxdo.callbackMissingToken'))
  await router.replace('/login')
  return
}
Defensive patterns

Strategy: type-guard

Type guard

function isLoginCompletion(c: PendingOAuthExchangeResponse): c is PendingOAuthExchangeResponse & { access_token: string } {
  return getOAuthCompletionKind(c) !== 'bind' && 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.linuxdo.callbackMissingToken')) {
    showError('Login session expired — please retry');
    await router.replace('/login'); return;
  }
  throw e;
}

Prevention

When it happens

Trigger: LinuxDo OAuth code exchange returns 2xx without access_token: state/session cookie lost between redirect and callback so the server returns an incomplete completion; response field renamed by a backend update; double-callback (user refreshes) consuming the one-time exchange.

Common situations: Strict cookie settings (ITP/Safari) dropping the OAuth session cookie across the redirect; frontend/backend version skew; callback URL opened twice; LinuxDo provider outage returning an error the backend wrapped as an empty completion.

Related errors


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