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
- 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.
- 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.
- Handle the double-submit/refresh case by making the exchange idempotent or redirecting the user to restart login cleanly.
- 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
- Make the completion-kind switch exhaustive: bind | login | pending | error, with an explicit default flow
- Make the exchange endpoint idempotent to survive callback refresh/double-submit
- Log the raw completion object server-side when returning a non-login shape
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
- auth.linuxdo.callbackMissingToken
- auth.oidc.callbackMissingToken
- auth.oidc.callbackMissingToken
- auth.verifyFailed
- dingtalk: internal_only requires app_type=internal
AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15).
Data as JSON: /api/errors/9f8ca6135726d769.
Report an issue: GitHub.