Ask
27

Verification fails with "jwt malformed" — the token looks fine when I print it

My API rejects requests with an error saying the token is malformed. It happens on every request, not intermittently.

When I log the token before verifying it, it looks like a normal token to me — a long string of characters.

The secret is right, because it is the same constant used to sign it. Signing works; verification does not.

What does malformed actually mean here, given the token appears to exist?

4 answers Share
Report

Answering anonymously — a moderator will review it first.

  • @token_shape_theo · yesterday

    "Malformed" is a very specific complaint and it happens before any signature checking: the library could not split the string into the three dot-separated parts a token is made of. So the secret is irrelevant — it never got that far — which is why signing working tells you nothing.

    A token is header.payload.signature, three base64url segments separated by two dots. Count the dots in what you are passing. If it is not exactly two, that is your answer, and the cause is nearly always one of these:

    1. You are passing the whole authorization header. The header value is Bearer <token>. Passing that string wholesale gives the library Bearer eyJ... which has a space and a word in front of the header segment. This is the most common cause by a wide margin, and the fix is to strip the scheme — split on whitespace and take the second part, and handle the case where the header is missing rather than indexing into nothing.

    2. The value is undefined, null or an empty string. A missing header, a cookie that was never set, a typo in the property name. Stringified, these become "undefined" or "", which is a string, so your log looks like it printed something. Log the type and the length alongside the value — undefined and a real token look far more different in that form.

    3. It is an object, not a string. If something already parsed or wrapped the token, you are handing the library the wrong type.

    4. It was truncated or mangled in transit. Cookies have size limits, and a token with a large payload can be silently cut off. Proxies occasionally mangle long headers. Compare the length at the point of issue with the length at the point of verification — if they differ, that is the whole problem and no amount of looking at the code will show it.

    5. It was double-encoded. Stored as JSON inside a cookie, or URL-encoded somewhere in the chain, so what arrives has quotes around it or percent-escapes in it. Quotes are the giveaway: a token with a leading " is a token that was JSON-stringified.

    How to find it in one minute. At the verification site, log three things: the length, the number of dots, and the first and last ten characters. That distinguishes every case above immediately, and it is much more informative than logging the token itself — which you should not be doing in anything but local debugging anyway, since a log with tokens in it is a log full of live credentials.

    Then decode without verifying. Every library has a decode function that parses the token without checking the signature. If decode succeeds and verify fails, you have a genuine signature or expiry problem and the causes are completely different — a mismatched secret, the wrong algorithm, or clock skew. If decode also fails with malformed, it is the string, and the list above applies.

    30
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @silent_renew_saskia · yesterday

    The advice to decode without verifying is the single most useful debugging habit for this whole area.

    Once you can see the decoded header and payload you can check, in seconds: the algorithm in the header matches what you expect, the issuer and audience are what you configured, and the expiry has not passed. Those three cover most verification failures that are not malformed strings.

    Just be disciplined about where you decode. Pasting a production token into a website to inspect it means handing a live credential to a third party — it happens constantly and it is genuinely a security incident. Decode locally, in your own process, with a library call.

    21
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @form_field_farid · 2d ago

    Once it is fixed, make sure a malformed token produces a clean 401 rather than a 500.

    The library throws, and if nothing catches it, an unauthenticated request with a junk header takes down the request with a server error and a stack trace. That is noise in your monitoring, it can leak internals in the response, and it means anyone can generate error-rate alerts by sending nonsense.

    Catch the verification errors explicitly and map them to a 401 with a generic message. Distinguish expiry internally if you want to prompt a refresh, but do not tell the caller which of the checks failed.

    14
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report
  • @spa_sami · 2d ago

    The truncation case is worth taking seriously if you are storing the token in a cookie, because it is silent and size-dependent.

    Browsers cap cookie size, and if you put a token with a large payload in one — several roles, a list of permissions, a profile — you can go over the limit. What happens then depends on the browser and it is not always a clean rejection.

    The symptom is characteristic and confusing: it works for users with small payloads and fails for users with many roles or long names. So it passes every test and breaks for the account with the most permissions, which is usually somebody senior.

    If that sounds like your situation, shrink the payload. A token should carry an identifier and a small number of claims, not a copy of the user record.

    26
    Share
    Reply

    Answering anonymously — a moderator will review it first.

    Report