{"record":{"id":"80a2a1fb31640342","repo":"Wei-Shaw/sub2api","slug":"auth-oidc-callbackmissingtoken","errorCode":null,"errorMessage":"auth.oidc.callbackMissingToken","messagePattern":"auth\\.oidc\\.callbackMissingToken","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/views/auth/OidcCallbackView.vue","lineNumber":607,"sourceCode":"    states.includes('bind_login_required') ||\n    states.includes('bind_login') ||\n    states.includes('adopt_existing_user_by_email') ||\n    states.includes('existing_account_required') ||\n    states.includes('existing_account_binding_required')\n}\n\nasync function finalizeCompletion(completion: PendingOAuthExchangeResponse, redirect: string) {\n  if (getOAuthCompletionKind(completion) === 'bind') {\n    const bindRedirect = sanitizeRedirectPath(completion.redirect || '/profile')\n    clearPendingAuthSession()\n    clearAllAffiliateReferralCodes()\n    appStore.showSuccess(bindSuccessMessage)\n    await router.replace(bindRedirect)\n    return\n  }\n\n  if (!isOAuthLoginCompletion(completion)) {\n    throw new Error(t('auth.oidc.callbackMissingToken'))\n  }\n\n  persistOAuthTokenContext(completion)\n  await authStore.setToken(completion.access_token)\n  clearAllAffiliateReferralCodes()\n  appStore.showSuccess(t('auth.loginSuccess'))\n  await router.replace(redirect)\n}\n\nasync function finalizePendingAccountResponse(completion: PendingOidcCompletion) {\n  applyAdoptionSuggestionState(completion)\n  const redirect = sanitizeRedirectPath(completion.redirect || redirectTo.value)\n\n  if (completion.error === 'invitation_required') {\n    pendingAccountAction.value = 'none'\n    needsInvitation.value = true\n    needsAdoptionConfirmation.value = false\n    isProcessing.value = false","sourceCodeStart":589,"sourceCodeEnd":625,"githubUrl":"https://github.com/Wei-Shaw/sub2api/blob/073e92d17178a1ccdb0a27017f572f10c9c7ab62/frontend/src/views/auth/OidcCallbackView.vue#L589-L625","documentation":"In frontend/src/views/auth/OidcCallbackView.vue:607, finalizeCompletion() processes the generic OIDC exchange completion. After handling the bind case, it requires isOAuthLoginCompletion(completion) (access_token present); otherwise it throws the localized 'auth.oidc.callbackMissingToken'. The error means the OIDC exchange endpoint returned a 2xx completion that is neither a bind nor a login result.","triggerScenarios":"OIDC authorization-code exchange returns an incomplete completion: session/state mismatch after the IdP redirect (cookie lost), backend token exchange with the IdP failed but was wrapped as success without a token, or the response schema changed (token under a different field name).","commonSituations":"SameSite=Lax/Strict cookies dropped on the IdP cross-site redirect; backend OIDC provider config wrong (client_secret/issuer) so no token is minted; frontend/backend version mismatch; callback processed twice via refresh.","solutions":["Check the exchange POST response body in devtools to see the actual completion fields.","Validate OIDC provider config (issuer, client_id/secret, redirect_uri) on the backend so token minting succeeds.","Ensure the pre-auth session cookie survives the IdP redirect (cookie SameSite, domain).","Add an explicit third branch: unknown completion -> friendly error + redirect to login instead of an uncaught throw."],"exampleFix":"// before\nif (!isOAuthLoginCompletion(completion)) {\n  throw new Error(t('auth.oidc.callbackMissingToken'))\n}\n\n// after\nif (!isOAuthLoginCompletion(completion)) {\n  if (isPendingCompletion(completion)) { await handlePending(completion); return }\n  appStore.showError(t('auth.oidc.callbackMissingToken'))\n  await router.replace('/login')\n  return\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function isOidcLoginCompletion(c: PendingOAuthExchangeResponse): c is PendingOAuthExchangeResponse & { access_token: string } {\n  return getOAuthCompletionKind(c) !== 'bind' && typeof (c as any).access_token === 'string' && (c as any).access_token.length > 0;\n}","tryCatchPattern":"try { await finalizeCompletion(completion, redirect); }\ncatch (e) {\n  if (e.message === t('auth.oidc.callbackMissingToken')) {\n    showError('Sign-in could not complete — please retry');\n    await router.replace('/login'); return;\n  }\n  throw e;\n}","preventionTips":["Validate IdP config (issuer, client_id/secret, redirect_uri) so token minting always succeeds","Verify the pre-auth session cookie survives the IdP redirect","Route unknown completion shapes to a friendly restart-login flow"],"tags":["oauth","oidc","callback","session","frontend"],"backgroundTag":null,"analyzedSha":"073e92d17178a1ccdb0a27017f572f10c9c7ab62","analyzedAt":"2026-08-15T14:33:00.750Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}