Error message
jwt malformed
What it means
jsonwebtoken's jwt.verify() could not even parse the token: it does not consist of three base64url segments separated by dots, or the header/payload segments do not decode to valid JSON. The signature was never checked — the string you passed is not structurally a JWT.
Why it happens
The Bearer prefix was not stripped
commonThe Authorization header value is "Bearer eyJ...", and the whole string — prefix included — was passed to jwt.verify(). "Bearer eyJhbGci..." starts with a segment that is not valid base64url JSON, so parsing fails immediately.
The wrong variable reached verify()
commonundefined, null, an empty string, "[object Object]" or a user id ended up where the token should be — typically a misspelled property (req.headers.authorisation), a missing cookie, or passing the decoded payload instead of the raw token.
The token was truncated in storage or transit
occasionalA VARCHAR(255) database column, a log line limit, or a copy-paste that lost the tail can cut the token so that fewer than three segments survive. ES256 tokens run ~200+ characters; RS256 tokens are far longer.
It is not a JWT at all
occasionalSome OAuth providers issue opaque access tokens (random strings) that are not JWTs. Passing a Google/GitHub opaque access_token to jwt.verify() fails here — only id_token (OpenID Connect) is guaranteed to be a JWT.
How to fix it
- 1.
Strip the scheme prefix before verifying
Take only the token part of the Authorization header, and guard against a missing header so you fail with a clear 401 instead of "jwt malformed".
js const auth = req.headers.authorization ?? ""; const [scheme, token] = auth.split(" "); if (scheme !== "Bearer" || !token) { return res.status(401).json({ error: "missing bearer token" }); } jwt.verify(token, publicKey, { algorithms: ["ES256"] }); - 2.
Log the raw value and count its segments
One log line tells you whether you are dealing with a prefix, a truncation or the wrong variable. A JWS has exactly 3 segments; a JWE has 5; anything else is not a token.
js console.log(JSON.stringify(token).slice(0, 80)); console.log("segments:", String(token).split(".").length); // must be 3 - 3.
Check what the token actually contains
Paste the string into the JWT debugger below: it decodes the header and claims locally and tells you precisely which segment is broken — or that the string is not a JWT at all.