decolua/9router · error · Error
Windsurf callback missing access_token
Error message
Windsurf callback missing access_token
What it means
parseWindsurfCallback throws this when the callback URL contains no error but also no access_token (or token) parameter, so there is no Firebase id token to continue with. The Windsurf flow uses response_type=token (implicit flow), so the token must arrive in the redirect fragment/query; its absence means the redirect was not a successful token callback.
Source
Thrown at src/lib/oauth/providers/windsurf.js:41
// Parse Windsurf callback (query string or full URL): ?access_token=...&state=...
function parseWindsurfCallback(raw, expectedState) {
const text = String(raw || "").trim();
let queryStr = text;
if (text.includes("?")) queryStr = text.slice(text.indexOf("?") + 1);
if (text.startsWith("#")) queryStr = text.slice(1);
const params = Object.fromEntries(new URLSearchParams(queryStr));
const pick = (keys) => {
for (const k of keys) { const v = params[k]; if (v && String(v).trim()) return String(v).trim(); }
return null;
};
const err = pick(["error"]);
if (err) {
const desc = pick(["error_description"]);
throw new Error(desc ? `Windsurf auth failed: ${err} (${desc})` : `Windsurf auth failed: ${err}`);
}
const accessToken = pick(["access_token", "token"]);
if (!accessToken) throw new Error("Windsurf callback missing access_token");
const state = pick(["state"]);
if (expectedState && state && state !== expectedState) {
throw new Error("Windsurf callback state mismatch");
}
return { firebaseIdToken: accessToken };
}
// POST RegisterUser {firebase_id_token} → {apiKey, apiServerUrl, name}
async function fetchWindsurfRegisterUser(firebaseIdToken) {
const data = await windsurfSeatRequest(WINDSURF_CONFIG.registerApiBaseUrl, WINDSURF_CONFIG.registerPath, {
firebase_id_token: firebaseIdToken,
});
const apiKey = extractJsonPath(data, [["apiKey"], ["api_key"]]);
if (!apiKey) throw new Error("Windsurf RegisterUser missing apiKey");
const apiServerUrl = extractJsonPath(data, [["apiServerUrl"], ["api_server_url"]]) || WINDSURF_CONFIG.defaultApiServerUrl;
const name = extractJsonPath(data, [["name"]]);
return { apiKey, apiServerUrl, name };
}View on GitHub (pinned to 90b52e06ff)
Solutions
- Ensure the user copies the FULL final URL from the browser address bar, including the #access_token=... fragment
- Confirm the local callback server (callbackPath) captured the fragment before redirecting — fragments only exist client-side
- Verify buildAuthUrl uses response_type=token and redirect_parameters_type=query so Windsurf appends the token
- Check the raw string is non-empty and is the redirect URL, not the auth page URL
- Retry the OAuth flow in a normal (non-embedded) browser
Example fix
// before: caller passes whatever the user typed
const tokens = await exchangeToken(config, rawPastedUrl, redirectUri, null, state);
// after: sanity-check before calling
if (!/(access_token=|token=)/.test(rawPastedUrl)) {
throw new Error('Pasted URL has no access_token — copy the full redirect URL including the # fragment');
}
const tokens = await exchangeToken(config, rawPastedUrl, redirectUri, null, state); Defensive patterns
Strategy: validation
Validate before calling
// validate the pasted/redirected URL contains a token before parsing
const hasToken = (raw) => {
const s = String(raw || '');
const q = s.slice(s.indexOf('?') + 1);
const frag = s.includes('#') ? s.slice(s.indexOf('#') + 1) : '';
const p = new URLSearchParams(q + (frag ? '&' + frag : ''));
return Boolean(p.get('access_token') || p.get('token'));
};
if (!hasToken(callbackUrl)) throw new Error('Callback URL has no access_token — copy the FULL final redirect URL'); Type guard
const isTokenCallback = (raw) => /(access_token=|token=)/.test(String(raw || ''));
Try / catch
try { ({ firebaseIdToken } = parseWindsurfCallback(raw, state)); }
catch (e) {
if (e.message === 'Windsurf callback missing access_token') {
throw new Error('No token in redirect — re-run sign-in and copy the complete URL including the #fragment');
}
throw e;
} Prevention
- Instruct users to copy the entire final URL, never just the visible path
- Remember URL fragments (#access_token) never reach a server — capture them client-side
- Confirm buildAuthUrl uses response_type=token with query redirect parameters
- Test the callback capture with a real browser before shipping
When it happens
Trigger: User pastes the wrong URL (e.g. the signin page URL instead of the final redirect); the redirect lost the hash fragment (fragments are not forwarded by intermediate servers); Windsurf redirected to a different page after login; empty/garbage string passed as raw.
Common situations: Manual paste-token flow where the user copies the wrong URL from the browser; redirect middleware stripping query params; hash-based tokens lost because the callback went through an HTTP redirect.
Related errors
- `Windsurf ${path} HTTP ${res.status}: ${text.slice(0, 200)}`
- `Windsurf ${path} invalid JSON`
- desc ? `Windsurf auth failed: ${err} (${desc})` : `Windsurf
- Windsurf callback state mismatch
- Windsurf RegisterUser missing apiKey
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/c5999dd6a9900ea7.
Report an issue: GitHub.