如果您想要本地存储,我建议您检查一下:https://www.npmjs.com/package/node-localstorage
但是,话虽如此,你们不会相信我花了多长时间从上面的答案中找到res.cookie('auth' token)。我搜索了 Google 几个小时、Passport 文档、Express 文档、GraphQL 和身份验证/授权文档,试图找出如何以无状态方式将令牌获取到 API。
我已经构建了 JWT 令牌安全性并使用它来保护我的 GraphQL 解析器,但是后来我选择使用 EJS 和 graphql-request(与 Apollo 客户端大致相同),所以我需要找到一种方法将令牌传递给我的中间件不使用服务器端会话。
在 cookie 中存储 JWT 令牌很好,特别是如果您采取额外的预防措施(例如签署 cookie),我记得您还可以包含一些选项来保证 cookie 的安全,这样如果“浏览器" 允许访问 cookie。如果 cookie 使用您的服务器机密签名,则 cookie 中的数据根本无法更改并且仍然有效。风险总是有人泄露他们的令牌/cookie,如果这让您感到困扰,请研究刷新令牌。但是,API 令牌通常并且应该严格保密且安全。如果您将到期时间设置为 1 年,您最大的烦恼很可能是需要维护一份从现在起一年后到期的 JWT 的黑名单。
我只是在这里包含我的发现,因为这个问题实际上似乎是一种稀有资源......
这是我用于身份验证的 Express 中间件:
// AUTHENTICATION
app.use(async (req) => {
try {
const token = req.headers.authorization || req.cookies.auth
const { person } = await jwt.verify(token, SECRET)
req.person = person
return req.next()
} catch (e) {
return req.next()
}
})
- 您可以看到我正在将标头中的令牌设置为 cookie 作为后备。这很好地支持了我的需求,并允许我使用任何具有无状态安全性的客户端。
- 在我的视图和 GraphQL 解析器中,我的登录用户为
req.person。如果 req.person 未设置,则用户被视为未登录。
- 我正在使用
return req.next(),这很重要,因为在没有参数的情况下调用next() 被视为“干净的转到下一个中间件和/或继续处理请求”。如果您包含任何字符串或对象参数,它会抛出一个错误,该错误会冒泡到您的错误处理中间件。你可以自己试试。将 return next('You are not authenticated.') 放在 catch 块中,您会看到它在您的路由之前停止请求。
- 我使用
return next(),因为我在路由和解析器中处理授权。它提供了更大的灵活性,例如便于未经身份验证的用户访问注册和登录突变。
这是我的 GraphQL 端点(我正在使用 Apollo Server):
app.use('/graphql', bodyParser.json(), graphqlExpress((req) => {
const context = {
person: req.person
}
return {
schema,
context,
rootValue: null
}
}))
- 在我的 GraphQL 解析器中,每个查询的第三个参数都有
context.person 填充有来自上述身份验证中间件的 req.person。
- 这就是一个人真正需要知道的一切。
下面是我如何使用名为graphql-request 的 NPM 包:
https://www.npmjs.com/package/graphql-request
app.get('/allpeople', async (req, res) => {
try {
const client = new GraphQLClient(GRAPHQL_ENDPOINT, {
headers: { Authorization: req.headers.authorization || req.cookies.auth }
})
const query = `query allPeople($serialNumber: String!) {
allPeople(serialNumber: $serialNumber) {
id
created
status
email
}
}`
const variables = {
serialNumber: req.person
}
const response = await client.request(query, variables)
res.render('allpeople/list', { people: response.allPeople })
} catch (e) {
throw [`allPeople`, `${JSON.stringify(error, null, 2)}`]
}
})
- 我包含此代码是因为没有“更高级”的 graphql-request 示例用法,到目前为止我很喜欢它。它非常简洁,如果您冒险使用 React.js,可以轻松地将其换成 Apollo Client。我这里的例子对于研究
createNetworkInterface 和new ApolloClient() 的人来说也非常相关。