错误信息
UnauthorizedError: No authorization token was found
也可能输出为
- No authorization token was found
- Format is Authorization: Bearer [token]
如果不处理这个错误,Express 会连同堆栈一起打印它,开头是“UnauthorizedError: No authorization token was found”。`err.code` 用来区分 `credentials_required`(没有令牌)与 `credentials_bad_scheme` 和 `credentials_bad_format`(“Format is Authorization: Bearer [token]”)。
express-jwt (Node.js) — UnauthorizedError, code credentials_required
含义
express-jwt 保护着这个路由,但没有找到令牌:请求中没有 `Authorization` 头,也没有自定义的 `getToken` 函数返回令牌。由于 `credentialsRequired` 默认为 `true`,中间件会抛出带有 `code: "credentials_required"` 和 `status: 401` 的 `UnauthorizedError`,请求永远到达不了你的处理函数。如果请求头存在、但格式不是 `Bearer <token>`,报出的则是“Format is Authorization: Bearer [token]”。
原因
客户端没有发送 Authorization 头
常见用户尚未登录、HTTP 客户端缺少添加该请求头的拦截器,或者令牌在过期后被清除了。在浏览器的网络面板中检查这个请求。
令牌通过 Cookie 或查询字符串传递
常见express-jwt 默认只读取 `Authorization: Bearer`。放在 HttpOnly Cookie、`?access_token=` 参数或自定义请求头中的令牌需要 `getToken` 函数——而 Cookie 还需要 `cookie-parser`。
代理删除了该请求头
偶见配置了显式请求头映射的 API 网关、只转发允许列表中请求头的代理,或者会清空该请求头的重写规则,都可能在请求到达 Node 之前丢弃 `Authorization`。浏览器中能看到这个请求头,服务器却从未收到。
中间件把公开路由也保护起来了
偶见在路由之前调用 `app.use(expressjwt(...))` 会保护所有内容,包括健康检查、登录和静态资源。发往这些路由的请求本来就不带令牌。
修复方法
- 1.
告诉 express-jwt 令牌在哪里
`getToken` 接收请求,返回令牌字符串或 `null`。让它保持简单,避免同时从多个位置接受令牌。
js import cookieParser from "cookie-parser"; import { expressjwt } from "express-jwt"; app.use(cookieParser()); app.use( expressjwt({ secret: getSigningKey, // e.g. jwks-rsa's expressJwtSecret(...) algorithms: ["RS256"], getToken: (req) => req.cookies.access_token ?? null, }), ); - 2.
豁免公开路由,或让身份验证变为可选
`.unless()` 会跳过列出的路径。`credentialsRequired: false` 会放行不带令牌的请求,此时没有 `req.auth`,由各个路由自行决定如何处理。
js app.use( expressjwt({ secret, algorithms: ["HS256"] }).unless({ path: ["/health", "/login", /^\/public\//] }), ); // Or keep authentication optional and decide per route: app.use(expressjwt({ secret, algorithms: ["HS256"], credentialsRequired: false })); - 3.
把错误转换为规范的 401 响应
Express 的默认错误处理函数会使用错误的 `status`(401),但响应的是一个 HTML 页面——在开发环境中还包含堆栈跟踪——而且没有 `WWW-Authenticate` 头。你需要自己把 `UnauthorizedError` 映射为带有该响应头的 JSON 401 响应。
js app.use((err, req, res, next) => { if (err.name === "UnauthorizedError") { return res.status(401).set("WWW-Authenticate", "Bearer").json({ error: err.code }); } next(err); });