jwt malformed (tested)

JsonWebTokenError: jwt malformed comes from the Node.js jsonwebtoken library. It means the string you handed to verify() or decode() is not shaped like a JWT at all — usually the wrong number of dot-separated segments. We broke tokens in four specific ways and recorded the exact message jsonwebtoken 9.0.3 gives for each, then ran the same strings through Python's PyJWT to show the wording is not standard across libraries.

1. What "jwt malformed" means

A JWT is three base64url segments joined by dots: header.payload.signature. Before jsonwebtoken checks a signature or an expiry, it checks that the string has that shape. If it doesn't, you get a JsonWebTokenError with the message jwt malformed, regardless of what else might be wrong with the string.

This is different from TokenExpiredError (right shape, bad timestamp) and from an invalid-signature error (right shape, wrong secret or tampered payload). "Malformed" means the parser never got far enough to check either of those.

2. Our test setup

We called jwt.verify(token, secret) from jsonwebtoken 9.0.3 on nine broken or borderline strings, then called jwt.decode(token, secret, algorithms=["HS256"]) from PyJWT 2.15.1 on the same strings. Versions:

$ node -e "console.log(require('jsonwebtoken/package.json').version)"
9.0.3
$ /tmp/py415venv/bin/python3 -c "import jwt; print(jwt.__version__)"
2.15.1

3. Wrong number of segments: jsonwebtoken says "jwt malformed", not "not enough segments"

Older JWT tooling (and some other libraries, see section 5) used the message not enough segments. We tested that phrase directly against jsonwebtoken 9.0.3 and it does not appear anywhere in its output. Any wrong segment count — too few or too many — collapses to the same generic message.

Inputjsonwebtoken 9.0.3 verify()
"abcdefgh" (no dots, 1 segment)JsonWebTokenError: "jwt malformed"
"abc.def" (2 segments)JsonWebTokenError: "jwt malformed"
"abc.def.ghi.jkl" (4 segments)JsonWebTokenError: "jwt malformed"
"" (empty string)JsonWebTokenError: "jwt must be provided"

The empty-string case is its own message, checked before the segment count. Everything else with the wrong number of segments — one, two, or four — produces the identical "jwt malformed" text. You cannot tell from the message alone how many segments the caller actually sent.

4. Invalid base64 and a corrupted signature give different messages again

Once a string has exactly three segments, jsonwebtoken moves past the shape check and into decoding. Bad base64 content and a tampered signature each produce their own distinct wording:

Input (3 segments, one part broken)jsonwebtoken 9.0.3 message
Header segment is !!!notbase64!!!JsonWebTokenError: "invalid token"
Payload segment is !!!notbase64!!!JsonWebTokenError: "invalid token"
Header decodes but isn't JSONJsonWebTokenError: "invalid token"
Valid token, last 4 chars of signature swappedJsonWebTokenError: "invalid signature"
Valid token, signature truncated to 5 charsJsonWebTokenError: "invalid signature"

So there are three distinct jsonwebtoken error strings you'll actually see for a broken token, each meaning something different: jwt malformed (wrong segment count), invalid token (right segment count, unparseable base64/JSON in header or payload), and invalid signature (everything parses, but the signature doesn't match — this is also what you get for a wrong secret, see the expired-vs-invalid-signature guide). All three are instances of the same JsonWebTokenError class, so catching by class alone won't distinguish them — check error.message.

Also worth knowing: jwt.decode() (no secret, no verification) just returns null on a bad-shape string instead of throwing. It never checks the signature, so don't use it to validate input.

> jwt.decode("abc.def")
null

5. PyJWT uses completely different wording for the same inputs

We ran the identical nine strings through PyJWT 2.15.1's jwt.decode(). The error class is DecodeError instead of JsonWebTokenError, and critically, PyJWT does say "Not enough segments" — the exact phrase that jsonwebtoken 9.0.3 does not produce.

Inputjsonwebtoken 9.0.3PyJWT 2.15.1
1 segment, no dotsjwt malformedDecodeError: Not enough segments
2 segmentsjwt malformedDecodeError: Not enough segments
Empty stringjwt must be providedDecodeError: Not enough segments
4 segmentsjwt malformedDecodeError: Invalid header string: 'utf-8' codec can't decode byte 0xb7 in position 1: invalid start byte
Invalid base64 in headerinvalid tokenDecodeError: Invalid header padding
Invalid base64 in payloadinvalid tokenDecodeError: Invalid payload padding
Corrupted signature (same length)invalid signatureDecodeError: Invalid crypto padding
Truncated signatureinvalid signatureDecodeError: Invalid crypto padding

Two things fall out of this side by side: first, if you're searching an error message verbatim, search for the library, not just "jwt malformed" — "not enough segments" is real, current, and comes from PyJWT, not from jsonwebtoken. Second, PyJWT's 4-segment case isn't caught by a segment-count check at all; it only checks for fewer than three, then tries to base64-decode whatever it has, so an over-long token surfaces as a confusing unicode-decode error instead of a clean "too many segments" message.

6. How to fix it

Frequently asked questions

Does jsonwebtoken ever say "not enough segments"?

Not in version 9.0.3, which we tested directly. Every wrong segment count (1, 2, or 4) produced "jwt malformed" instead. "Not enough segments" is the message PyJWT 2.15.1 uses for the same situation in Python, so if you see that exact phrase you're very likely debugging a Python stack, not Node.

Is "jwt malformed" the same error as "invalid signature"?

No. Both are JsonWebTokenError in jsonwebtoken, but they mean different things. "jwt malformed" means the string doesn't have three valid segments. "invalid signature" means it does, but the signature doesn't match the payload and secret — we measured both by deliberately breaking each case.

What if the token has exactly 3 segments but still fails?

Then it's not a segment-count problem. In our tests, invalid base64 inside a segment produced "invalid token", and a tampered or truncated signature produced "invalid signature" — both distinct from "jwt malformed".

Does jwt.decode() throw on a malformed token?

No, in our test jwt.decode() (jsonwebtoken 9.0.3, no secret passed) returned null for a 2-segment string instead of throwing. Only jwt.verify() and jwt.decode() with options that require parsing throw JsonWebTokenError; use our jwt-decoder tool if you just want to inspect a token's shape without writing code.

Why does my token look fine but still say malformed?

A common cause we didn't have to simulate: the caller sent the full Authorization header value, like "Bearer eyJhbGc...", straight into verify(). That string has the right segments after "Bearer " but jsonwebtoken sees the "Bearer " prefix stuck to the first segment and the whole thing fails the shape check. Strip the prefix first.

Written with AI assistance. Measured on 2026-10-02 with jsonwebtoken 9.0.3 on Node.js 24.21.0, and PyJWT 2.15.1 on Python 3.13, run against the same set of broken token strings. The jwt-decoder tool is ours and free; jsonwebtoken and PyJWT are open source.