エラーメッセージ
jwt malformed
このエラーの意味
jsonwebtoken の jwt.verify() は、トークンを解析することすらできませんでした。文字列をドットで区切っても、ちょうど 3 つのセグメントになりません。署名の検証は行われていません。渡した文字列は、構造上 JWT ではありません。(セグメントが 3 つあっても、ヘッダーが base64url の JSON ではない文字列は、1 段階後に「invalid token」で失敗します。)
発生する理由
Authorization ヘッダーの分割方法が間違っている
よくあるAuthorization ヘッダーの値は「Bearer eyJ...」ですが、コードが間違ったほうの半分を残しています。auth.split(" ")[0] は、ドットをまったく含まないスキーム「Bearer」そのものを jwt.verify() に渡します。「Bearer eyJhbGci...」という文字列全体を渡した場合は、失敗の仕方が異なります。この文字列も 3 つのセグメントに分割されるため、最初のセグメントをデコードできない時点で、jsonwebtoken は「invalid token」を報告します。
verify() に誤った変数が渡された
よくあるトークンがあるべき場所に、トークンではない文字列が入っています。存在しない値を文字列に埋め込んで生じた「undefined」や「null」(何も保存されていなかったために「Bearer null」を送信するクライアント、綴りを誤った req.headers.authorisation、存在しない Cookie)、生のトークンを渡さずにデコード済みのペイロードを文字列化して生じた「[object Object]」、あるいはユーザー ID などです。本物の undefined、null、空文字列は、1 つ手前のチェックで「jwt must be provided」として検出されます。
保存時または転送中にトークンが切り詰められた
ときどきVARCHAR(255) のデータベースカラム、ログの 1 行あたりの長さ制限、コピーして貼り付けたときの末尾の欠落によってトークンが切れ、残るセグメントが 3 つ未満になることがあります。ES256 のトークンは約 200 文字以上になり、RS256 のトークンはそれよりはるかに長くなります。
そもそも JWT ではない
ときどきOAuth プロバイダーの中には、JWT ではない不透明なアクセストークン(ランダムな文字列)を発行するものがあります。Google や GitHub の不透明な access_token を jwt.verify() に渡すと、ここで失敗します。JWT であることが保証されているのは id_token(OpenID Connect)だけです。
修正方法
- 1.
検証の前にスキームのプレフィックスを取り除く
Authorization ヘッダーからトークン部分だけを取り出し、ヘッダーがない場合にも備えてください。そうすれば、「jwt malformed」ではなく明確な 401 で失敗させることができます。
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.
生の値をログに出力してセグメントを数える
ログを 1 行出力するだけで、原因がプレフィックスなのか、切り詰めなのか、誤った変数なのかがわかります。JWS のセグメントはちょうど 3 つ、JWE は 5 つです。それ以外はトークンではありません。
js console.log(JSON.stringify(token).slice(0, 80)); console.log("segments:", String(token).split(".").length); // must be 3 - 3.
トークンに実際に含まれている内容を確認する
下の JWT デバッガーに文字列を貼り付けてください。ヘッダーとクレームをローカルでデコードし、どのセグメントが壊れているのか、あるいはその文字列がそもそも JWT ではないことを正確に示します。