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 Apollo 客户端的订阅组件

使用 Apollo 客户端的片段组合:约定和样板

graphql_devise 用于 Rails/Graphql/Apollo/React 中的身份验证。 “字段需要身份验证”错误

TypeError:试图分配给只读属性。在 Expo / GraphQL Apollo 客户端上

如何重置 Apollo 客户端的 useMutation 钩子