{"record":{"id":"a309f5c6950a07c1","repo":"actualbudget/actual","slug":"no-token-received","errorCode":null,"errorMessage":"No token received.","messagePattern":"No token received\\.","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"packages/desktop-electron/index.ts","lineNumber":130,"sourceCode":"          );\n        } else {\n          void clientWin.loadURL(`app://actual/openid-cb?token=${code}`);\n        }\n\n        // Respond to the browser\n        res.writeHead(200, { 'Content-Type': 'text/plain' });\n        res.end('OpenID login successful! You can close this tab.');\n\n        // Clean up the server after receiving the code. Wait for the listener\n        // to fully release port 3010 before clearing the reference, otherwise a\n        // subsequent start-oauth-server request could try to bind the port\n        // while this listener is still shutting down.\n        await new Promise<void>(closeResolve => {\n          server.close(() => closeResolve());\n        });\n        oAuthServer = null;\n      } else {\n        res.writeHead(400, { 'Content-Type': 'text/plain' });\n        res.end('No token received.');\n      }\n    });\n\n    server.listen(port, '127.0.0.1', () => {\n      logMessage('info', `OAuth server started on port: ${port}`);\n      resolve({ url: `http://localhost:${port}`, server });\n    });\n  });\n};\n\nif (isDev) {\n  process.traceProcessWarnings = true;\n}\n\nasync function loadGlobalPrefs() {\n  let state: GlobalPrefsJson = {};\n  try {","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/desktop-electron/index.ts#L112-L148","documentation":"In the desktop (Electron) app's OAuth callback, an inline HTTP server listens on 127.0.0.1 for the provider's redirect. If the request arrives without the expected token query parameter (the handler closes the server when a token is received, otherwise it responds), the server replies 400 with the plain-text body 'No token received.' and the OAuth flow fails.","triggerScenarios":"The OAuth identity provider redirects back to http://127.0.0.1:<port> without a token (or code-derived token) query parameter — e.g. the user cancels login at the provider, the provider sends an error response, or the redirect URL was modified/corrupted so the token param is missing.","commonSituations":"User cancels the OAuth consent screen; provider redirects with error=access_denied instead of a token; a mismatch between the configured redirect port and the app's listening port causing a malformed/foreign request to hit the callback; browser extensions stripping query params.","solutions":["Retry the sign-in flow and complete authorization at the provider instead of cancelling.","Check the provider's redirect for an error query parameter (access_denied etc.) and address the underlying cause (app not approved, account blocked).","Verify the OAuth redirect URI / port configured in the app matches what the provider is allowed to redirect to.","Check for browser extensions or proxies that alter the redirect URL and drop the token parameter."],"exampleFix":"// before: provider redirect missing token\nhttp://127.0.0.1:3999/oauth?state=xyz\n// after: redirect must include the token\nhttp://127.0.0.1:3999/oauth?token=abc123&state=xyz","handlingStrategy":"validation","validationCode":"const parsed = new URL(redirectUrl, 'http://127.0.0.1');\nif (!parsed.searchParams.get('token')) {\n  const err = parsed.searchParams.get('error');\n  throw new Error(`OAuth redirect missing token${err ? ` (provider error: ${err})` : ''}`);\n}","typeGuard":"function hasOAuthToken(u: URL): boolean {\n  return typeof u.searchParams.get('token') === 'string' && u.searchParams.get('token')!.length > 0;\n}","tryCatchPattern":"try {\n  const token = await startOAuthFlow(); // resolves with token from local callback server\n} catch (e) {\n  if (e.message.includes('No token received')) {\n    logger.warn('OAuth login cancelled or redirect lacked a token; prompting user to retry');\n  }\n}","preventionTips":["Complete the authorization flow instead of cancelling at the consent screen.","Verify the OAuth redirect port/URI matches the app's local listener configuration.","Check provider error query params (error=access_denied) in callback handling and surface them to the user.","Test the flow with browser extensions/proxies disabled to rule out URL tampering."],"tags":["oauth","electron","http-400","authentication"],"backgroundTag":"oauth-callback-missing-token","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}