エラーメッセージ
jwt must be provided
jsonwebtoken (Node.js) — JsonWebTokenError from jwt.verify()
このエラーの意味
`jwt.verify()` にトークンがまったく渡されていません。第 1 引数が `undefined`、`null`、または空文字列でした。jsonwebtoken は解析の前にこれを確認するため、この時点では鍵もトークンの形式もまだ関係ありません。問題はその手前にあり、リクエストからトークンを取り出すコードが何も見つけられなかったということです。
発生する理由
クライアントが Authorization ヘッダーを送信していない
よくあるリクエストがトークンなしでルートに到達しています。ユーザーがログインしていなかった、クライアントがヘッダーを付け忘れた、あるいはクライアント側でトークンが期限切れになり削除された、といった場合です。このとき、`req.headers.authorization.split(" ")[1]` のようなコードは `undefined` を返します。
トークンを誤った場所から読み取っている
よくあるプロパティ名の綴りの誤り(`authorisation`、小文字化されたヘッダーマップに対する `Authorization`)、`x-access-token` のようなカスタムヘッダー、コードが読み取っていないクエリパラメーターなどです。Node は受信したヘッダー名を小文字に変換するため、`req.headers.Authorization` は常に `undefined` になります。
セッション Cookie が送信されていない
ときどきクロスオリジンのリクエストでは、クライアントが `credentials: "include"` を設定し、かつ Cookie が `SameSite=None; Secure` でない限り、Cookie は送信されません。cookie-parser ミドルウェアがない場合も、`req.cookies` は空になります。
トークンがまだ用意されていない
ときどきログインフローがトークンを保存する前にデータを取得するフロントエンドや、ストレージからトークンを非同期に読み取るフロントエンドは、最初のリクエストを空の値で送信します。
修正方法
- 1.
トークンを防御的に取り出し、401 を返す
`verify` を呼び出す前にトークンがないかを確認し、`WWW-Authenticate: Bearer` ヘッダーを付けて 401 を返してください。そうすれば、クライアントには、捕捉されなかったエラーによる 500 ではなく「未認証」という応答が返ります。
js function bearerToken(req) { const [scheme, token] = (req.headers.authorization ?? "").split(" "); return scheme?.toLowerCase() === "bearer" && token ? token : null; } app.use((req, res, next) => { const token = bearerToken(req); if (!token) return res.status(401).set("WWW-Authenticate", "Bearer").end(); try { req.user = jwt.verify(token, publicKey, { algorithms: ["ES256"] }); next(); } catch { res.status(401).set("WWW-Authenticate", 'Bearer error="invalid_token"').end(); } }); - 2.
クライアントにトークンを付けさせる
ブラウザーのネットワークパネルで、リクエストに `Authorization: Bearer …`(または Cookie)が含まれていることを確認してください。ブラウザーではヘッダーが見えるのにサーバーには届いていない場合は、間にあるプロキシがヘッダーを削除しています。
js // Browser / Node 18+ fetch: the token must be attached explicitly. await fetch("https://api.example.com/orders", { headers: { Authorization: "Bearer " + accessToken }, }); // Cookies cross origins only with credentials and SameSite=None; Secure. await fetch("https://api.example.com/orders", { credentials: "include" });