【问题标题】:Invalid Auth Token with Rails, Graphql, Apollo ClientRails、Graphql、Apollo 客户端的 Auth Token 无效
【发布时间】: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)

【问题讨论】:

    标签: ruby-on-rails graphql authenticity-token apollo-client


    【解决方案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
            
            

            希望这会有所帮助。

            干杯

            【讨论】:

              猜你喜欢
              • 2018-03-25
              • 2019-06-22
              • 1970-01-01
              • 2022-12-12
              • 2016-08-31
              • 2020-09-19
              • 2019-06-30
              • 2019-06-10
              • 2020-07-22
              相关资源
              最近更新 更多