{"record":{"id":"c138177ee571374d","repo":"HeyPuter/puter","slug":"token-missing-c13817","errorCode":"token_missing","errorMessage":"Missing `token`","messagePattern":"Missing `token`","errorType":"exception","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"src/backend/controllers/wisp/WispController.ts","lineNumber":110,"sourceCode":"        } else {\n            const token = this.services.token.sign(\n                'wisp',\n                {\n                    $: 'token:wisp',\n                    $v: '0.0.0',\n                    guest: true,\n                },\n                { expiresIn: '1d' },\n            );\n            res.json({ token, server: wispCfg.server ?? null });\n        }\n    };\n\n    /** POST /wisp/relay-token/verify — verify a relay token and apply policy. */\n    #verify = async (req: Request, res: Response): Promise<void> => {\n        const bodyToken = req.body?.token;\n        if (!bodyToken || typeof bodyToken !== 'string') {\n            throw new HttpError(400, 'Missing `token`', {\n                legacyCode: 'token_missing',\n            });\n        }\n\n        let decoded: Record<string, unknown>;\n        try {\n            decoded = this.services.token.verify<Record<string, unknown>>(\n                'wisp',\n                bodyToken,\n            );\n            if (decoded.$ !== 'token:wisp')\n                throw new HttpError(403, 'wrong token type', {\n                    legacyCode: 'invalid_token',\n                });\n        } catch {\n            throw new HttpError(403, 'Forbidden', {\n                legacyCode: 'invalid_token',\n            });","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/wisp/WispController.ts#L92-L128","documentation":"Thrown by the WISP relay-token verify endpoint (POST /wisp/relay-token/verify on the api.* subdomain) when req.body.token is falsy or not a string. It is the input gate before the token is JWT-verified by TokenService. Returns 400 with legacyCode token_missing.","triggerScenarios":"A POST to /wisp/relay-token/verify whose JSON body omits the token field, sets it to null/empty, sends a non-string type (number/object), or whose body parser failed to populate req.body (wrong Content-Type).","commonSituations":"Client forgot to include the token in the JSON body; sent it as a query param or header instead of the body; wrong Content-Type (not application/json) so Express did not parse the body; token variable undefined due to a prior step failing; rate limit (300/min per IP) masking the real cause.","solutions":["Send a JSON body { \"token\": \"<jwt-string>\" } with Content-Type: application/json.","Obtain the token first from POST /wisp/relay-token/create and pass its response.token into verify.","Confirm the token variable is defined and is a string before the request (guard against undefined).","Verify the HTTP client is sending the body as JSON, not form-encoded."],"exampleFix":"// before\nawait fetch('/wisp/relay-token/verify', {\n  method: 'POST',\n  body: JSON.stringify({}), // token omitted\n});\n\n// after\nconst { token } = await (await fetch('/wisp/relay-token/create', { method: 'POST' })).json();\nawait fetch('/wisp/relay-token/verify', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ token }),\n});","handlingStrategy":"validation","validationCode":"function validTokenBody(body) {\n  return body != null && typeof body.token === 'string' && body.token.length > 0;\n}\nif (!validTokenBody(req.body)) {\n  throw new Error('Request body must include a string `token`');\n}","typeGuard":"function isTokenBody(body) {\n  return body != null && typeof body === 'object' && typeof body.token === 'string';\n}","tryCatchPattern":"try {\n  await fetch('/wisp/relay-token/verify', { method: 'POST', body: JSON.stringify({ token }) });\n} catch (e) {\n  if (e.status === 400 && /token/.test(e.message)) {\n    const created = await fetch('/wisp/relay-token/create', { method: 'POST' });\n    token = (await created.json()).token;\n    await fetch('/wisp/relay-token/verify', { method: 'POST', body: JSON.stringify({ token }) });\n  } else throw e;\n}","preventionTips":["Always mint a token from /wisp/relay-token/create first and pass response.token into verify.","Send Content-Type: application/json so Express parses the body into req.body.","Guard against undefined token variables before constructing the request.","Do not send the token as a query param or header — it must be in the JSON body."],"tags":["wisp","http","token","validation","jwt"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}