Rails、Graphql、Apollo 客户端的 Auth Token 无效
Posted
技术标签:
【中文标题】Rails、Graphql、Apollo 客户端的 Auth Token 无效【英文标题】:Invalid Auth Token with Rails, Graphql, Apollo Client 【发布时间】:2017-08-01 03:05:52 【问题描述】:我正在尝试使基本的 Rails、Graphql、Apollo-Client 设置正常工作,但在 rails 端出现 422 错误“无效的身份验证令牌”时遇到问题。
我对阿波罗的使用看起来不对吗?
这是一个带有 graphql gem 和 apollo 客户端的 Rails 5 应用程序。
const csrfToken = document.getElementsByName('csrf-token')[0].content
const client = new ApolloClient(
networkInterface: createNetworkInterface(
uri: '/graphql',
credentials: 'same-origin',
headers:
'X-CSRF-Token': csrfToken
),
);
client.query(
query: gql`
query Camillo2
user
id
email
created_at
`,
)
.then(data => console.log(data))
.catch(error => console.error(error));
Rails 日志:
Started POST "/graphql" for ::1 at 2017-03-10 08:51:58 -0800
Processing by GraphqlController#create as */*
Parameters: "query"=>"query Camillo2 \n user \n id\n email\n created_at\n __typename\n \n\n", "operationName"=>"Camillo2", "graphql"=>"query"=>"query Camillo2 \n user \n id\n email\n created_at\n __typename\n \n\n", "operationName"=>"Camillo2"
Can't verify CSRF token authenticity.
Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms)
【问题讨论】:
【参考方案1】:前几天我在用 Apollo on Rails 进行试验,下面是对我有用的配置:
var RailsNetworkInterface = apollo.createNetworkInterface('/graphql',
credentials: 'same-origin',
headers:
'X-CSRF-Token': $("meta[name=csrf-token]").attr("content"),
);
var client = new apollo.ApolloClient(networkInterface: RailsNetworkInterface)
client.query( query: gql` event(id: 7) name `)
但是,您的似乎也可以!
您能否确认正在向服务器发送正确的令牌?比如你打开Chrome devtools的network tab,然后进行查询,能看到“Request Headers”部分的X-CSRF-Token
吗?
此外,Rails 可能正在更新 DOM 中的 CSRF 令牌,但 NetworkInterface 保留了旧的、陈旧的 CSRF 令牌。您能否确认“请求标头”部分中的令牌与 <meta>
标记中的 current 值匹配? (我很惊讶自己在使用 Turbolinks Classic 时没有遇到这个问题。)
【讨论】:
感谢您的示例和想法。奇怪但通过切换我的查询以将 url 作为第一个 arg 传递(根据您的示例)不再导致 422 错误。尽管 Apollo 告诉我这已被弃用,并且我在上面的语法中使用 uri: "..." 是首选!【参考方案2】:这对我有用(我正在使用 react_on_rails):
import ApolloClient, createNetworkInterface from 'react-apollo';
import ReactOnRails from 'react-on-rails';
const networkInterface = createNetworkInterface(
uri: '/graphql',
opts:
credentials: 'same-origin'
);
networkInterface.use([
applyMiddleware(req, next)
if (!req.options.headers)
req.options.headers =
"X-CSRF-Token": ReactOnRails.authenticityToken()
next();
]);
const client = new ApolloClient(
networkInterface
);
export default client
我错过了 opts: credentials: 'same-origin' ,导致同样的错误:无法验证 CSRF 令牌的真实性。 已完成 422 无法处理的实体
【讨论】:
【参考方案3】:为了澄清上述一些答案,因为其中一位用户指出,将 uri 作为第一个参数传递给 createNetworkInterface 是可行的,但会在控制台中记录一条弃用消息:您必须传入“凭据”和“ headers”作为选项对象的属性,如下所示:
const networkInterface = createNetworkInterface(
uri: '/graphql',
opts:
credentials: 'same-origin',
headers:
'X-CSRF-TOKEN': $('meta[name=csrf-token]').attr('content'),
,
,
);
请参阅文档中 Fetch 下的第二个示例:http://dev.apollodata.com/core/network.html
【讨论】:
【参考方案4】:自 Apollo 2.0 以来并根据
https://github.com/apollographql/apollo-client/blob/master/Upgrade.md
就这样做
const csrfToken = document.querySelector('meta[name=csrf-token]').getAttribute('content');
const client = new ApolloClient(
link: new HttpLink(
credentials: 'same-origin',
headers:
'X-CSRF-Token': csrfToken
),
cache: new InMemoryCache()
);
【讨论】:
【参考方案5】:使用 apollo-boost 有点不同。
import ApolloClient from 'apollo-boost'
const client = new ApolloClient(
fetchOptions:
credentials: 'same-origin',
,
request: (operation) =>
const csrfToken = document.querySelector('meta[name=csrf-token]').getAttribute('content')
operation.setContext(
headers: "X-CSRF-Token": csrfToken
)
,
)
【讨论】:
只记得在布局中添加 !【参考方案6】:这个对我来说很好用。
const csrfToken = document.querySelector('meta[name=csrf-token]').getAttribute('content');
const link = createHttpLink(
uri: '/graphql',
credentials: 'same-origin',
headers:
'X-CSRF-Token': csrfToken
);
const client = new ApolloClient(
cache: new InMemoryCache(),
link,
);
【讨论】:
【参考方案7】:在触发 Apollo 查询客户端(在我的情况下为 React)时,我自己在 /graphql
端点上遇到了 422 错误。
可能会发生两件大事。
1.) 制作三重、四重、五重……确保您没有输入任何拼写错误。 Apollo 客户端配置可以变得非常简洁,因此请对您编写的令牌设置代码非常谨慎。
// Get Token from Meta Tags on Application.html.erb
const getToken = () => document.querySelector('meta[name="csrf-token"]').getAttribute('content');
const token = getToken();
// MiddleWare Operationt that sets the CSRF token on requests to protect from forgery
const setTokenForOperation = async operation =>
operation.setContext(
headers:
'X-CSRF-Token': token,
,
);
// Link With CSRF Token
const createLinkWithToken = () =>
new ApolloLink(
(operation, forward) =>
new Observable(observer =>
let handle;
Promise.resolve(operation)
.then(setTokenForOperation)
.then(() =>
handle = forward(operation).subscribe(
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
);
)
.catch(observer.error.bind(observer));
return () =>
if (handle) handle.unsubscribe();
;
)
);
... other apollo-link middleware stuff like error logging etc
// Tell Apollo client about the endpoint for making queries: (HTTP LINK) - this was default endpoint added by graphql install
const createHttpLink = () =>
new HttpLink(
uri: '/graphql',
credentials: 'include',
);
// and finally put it all together
export const createClient = (cache, requestLink) =>
new ApolloClient(
link: ApolloLink.from([createErrorLink(), createLinkWithToken(), createHttpLink()]),
cache,
);
2.) 防止伪造会话在 3rd 方身份验证策略(即设计 + CanCan 或 w/e)上失败
如果您的 apollo 中间件模式符合犹太教规,那么您的身份验证策略可能会阻碍请求。
例如,我正在使用 devise + cancan,这也导致了一些看似随机的 422,通过一些研究很容易解决(参见这篇文章 https://blog.bigbinary.com/2016/04/06/rails-5-default-protect-from-forgery-prepend-false.html)
在这部分长话短说,您可能需要让应用程序控制器(尤其是如果您从旧版本升级)加载并授权您当前的用户/资源在防止伪造设置之后(参考文章的更多细节)
class ApplicationController < ActionController::Base
protect_from_forgery prepend: true, with: :exception
...
before_action .. whatever else you have going on hereafter
希望这会有所帮助。
干杯
【讨论】:
以上是关于Rails、Graphql、Apollo 客户端的 Auth Token 无效的主要内容,如果未能解决你的问题,请参考以下文章
Apollo GraphQL - 如何将 RxJS 主题用作 Apollo 客户端的变量?
graphql_devise 用于 Rails/Graphql/Apollo/React 中的身份验证。 “字段需要身份验证”错误