如何使用Apollo和GraphQL在Nextjs中实现CSRF保护

如何使用Apollo和GraphQL在Nextjs中实现CSRF保护,graphql,csrf,apollo,next.js,express-session,Graphql,Csrf,Apollo,Next.js,Express Session,接下来在Nextjs存储库中,我想实现CSRF保护(可能是使用包),因为我在express session中使用会话ID cookie 我尝试在自定义服务器中设置csurf,并将生成的令牌保存在res.locals.csrfToken中,在第一页加载时,可以通过静态方法“getInitialProps”获取该令牌,该方法位于我链接的示例中的/lib/withApollo.js中。当我尝试更改页面(带有链接)或尝试使用apollo(例如登录)发出post请求时,服务器会更改csrf令牌,因此apo

接下来在Nextjs存储库中,我想实现CSRF保护(可能是使用包),因为我在express session中使用会话ID cookie

我尝试在自定义服务器中设置csurf,并将生成的令牌保存在res.locals.csrfToken中,在第一页加载时,可以通过静态方法“getInitialProps”获取该令牌,该方法位于我链接的示例中的/lib/withApollo.js中。当我尝试更改页面(带有链接)或尝试使用apollo(例如登录)发出post请求时,服务器会更改csrf令牌,因此apollo使用的那个令牌不再有用,因此我得到一个“csrf无效”错误

具有csurf配置的自定义服务器

const csrf = require('csurf');
const csrfProtection = csrf();
////express-session configuration code////
app.use(csrfProtection);
app.use((req, res, next) => {
    res.locals.csrfToken = req.csrfToken();
next();
})
/lib/initApollo.js

function create(initialState, { getToken, cookies, csrfToken }) {
  const httpLink = createHttpLink({
    uri: "http://localhost:3000/graphql",
    credentials: "include"
  });

    const authLink = setContext((_, { headers }) => {
    const token = getToken();
    return {
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : "",
        Cookie: cookies ? cookies : "",
        "x-xsrf-token": csrfToken ? csrfToken : ""
      }
    };
  });
static async getInitialProps(ctx) {
  const {
    Component,
    router,
    ctx: { req, res }
  } = ctx;
  const apollo = initApollo(
    {},
    {
      getToken: () => parseCookies(req).token,
      cookies: req ? req.headers.cookie : "",
      csrfToken: res ? res.locals.csrfToken : document.cookie
    }
  );
import React, { useEffect } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import Head from 'next/head';

export default function MyApp(props) {
  const { Component, pageProps } = props;

  useEffect(() => {
    // Get the XSRF-TOKEN from cookies
    function getCookie(name) {
      const value = `; ${document.cookie}`;
      const parts = value.split(`; ${name}=`);
      if (parts.length === 2) return parts.pop().split(';').shift();
    }

    // set the 'csrf-token' as header on Axios POST requests only (please see csurf docs to see which other headers they accept)
    // you could also add PUT or PATCH if you wish
    axios.defaults.headers.post['csrf-token'] = getCookie('XSRF-TOKEN');

    // The rest of your UseEffect code (if any).....
  }, []);

  // Your app
  return (
    <React.Fragment>
      <Head></Head>
      <Navbar />
      <Component {...pageProps} />
      <Footer />
    </React.Fragment>
  );
}

MyApp.propTypes = {
  Component: PropTypes.elementType.isRequired,
  pageProps: PropTypes.object.isRequired,
};

/lib/withApollo.js

function create(initialState, { getToken, cookies, csrfToken }) {
  const httpLink = createHttpLink({
    uri: "http://localhost:3000/graphql",
    credentials: "include"
  });

    const authLink = setContext((_, { headers }) => {
    const token = getToken();
    return {
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : "",
        Cookie: cookies ? cookies : "",
        "x-xsrf-token": csrfToken ? csrfToken : ""
      }
    };
  });
static async getInitialProps(ctx) {
  const {
    Component,
    router,
    ctx: { req, res }
  } = ctx;
  const apollo = initApollo(
    {},
    {
      getToken: () => parseCookies(req).token,
      cookies: req ? req.headers.cookie : "",
      csrfToken: res ? res.locals.csrfToken : document.cookie
    }
  );
import React, { useEffect } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import Head from 'next/head';

export default function MyApp(props) {
  const { Component, pageProps } = props;

  useEffect(() => {
    // Get the XSRF-TOKEN from cookies
    function getCookie(name) {
      const value = `; ${document.cookie}`;
      const parts = value.split(`; ${name}=`);
      if (parts.length === 2) return parts.pop().split(';').shift();
    }

    // set the 'csrf-token' as header on Axios POST requests only (please see csurf docs to see which other headers they accept)
    // you could also add PUT or PATCH if you wish
    axios.defaults.headers.post['csrf-token'] = getCookie('XSRF-TOKEN');

    // The rest of your UseEffect code (if any).....
  }, []);

  // Your app
  return (
    <React.Fragment>
      <Head></Head>
      <Navbar />
      <Component {...pageProps} />
      <Footer />
    </React.Fragment>
  );
}

MyApp.propTypes = {
  Component: PropTypes.elementType.isRequired,
  pageProps: PropTypes.object.isRequired,
};


使用此配置,每个路由都会受到csrf的保护,但服务器上创建的令牌经常会更改,Apollo无法在需要时立即检索更新的令牌,因此第一次加载成功,但随后的页面更改(链接)或任何post请求都会失败,因为令牌已更改。

这可能不是您要寻找的答案。我已经读到,如果您使用JWT,就不需要CSRFToken。我不完全确定,但这是目前唯一的办法

解释如下:

我发现了一些关于CSRF+不使用Cookie进行身份验证的信息:

“由于您不依赖Cookie,因此不需要防止跨站点请求”

“如果我们走Cookie的道路,你真的需要做CSRF来避免跨站点请求。正如你将看到的,在使用JWT时,我们可能会忘记这一点。” (JWT=Json Web令牌,一种针对无状态应用程序的基于令牌的身份验证)

“在不冒CSRF漏洞风险的情况下进行身份验证的最简单方法是避免使用cookie识别用户”

“CSRF的最大问题是cookie绝对无法抵御此类攻击。如果您使用cookie身份验证,您还必须采取其他措施来防范CSRF。您可以采取的最基本的预防措施是确保您的应用程序不会对GET reque执行任何副作用sts。”

如果不使用cookies进行身份验证,那么还有很多页面说明不需要任何CSRF保护。当然,您仍然可以将cookie用于其他任何内容,但避免在cookie中存储会话id之类的内容

全文如下:

更新 经过这么多的浏览,我终于能够发送csrf cookie了。我认为问题在于
return
这个词。当您使用return时,它排除了cookie。这就是我通过编辑
/lib/initApollo.js
所做的

function create(initialState, { getToken, cookies, csrfToken }) { const httpLink = createHttpLink({ uri: "http://localhost:3000/graphql", credentials: "include" }); const authLink = setContext((_, { headers }) => { const token = getToken(); return { headers: { ...headers, authorization: token ? `Bearer ${token}` : "", "x-xsrf-token": csrfToken ? csrfToken : "" } cookies: { ...cookies } }; }); 函数create(initialState,{getToken,cookies,csrfToken}){ const httpLink=createHttpLink({ uri:“http://localhost:3000/graphql", 凭据:“包括” }); const authLink=setContext(({headers})=>{ const token=getToken(); 返回{ 标题:{ …标题, 授权:令牌?`Bearer${token}`:“”, “x-xsrf-token”:csrfToken?csrfToken:” } 曲奇饼:{ …饼干 } }; });
pree!!但是SSR没有Cookie。我认为我们应该有两个来自客户端的端点和一个用于SSR的端点。SSR url可以不使用csrf。

对于那些不使用express session的人,下面的代码也适用于我。我希望这能帮助其他可能需要它的人。我使用的是自定义express server,这是我的imp的简化版本营养素

Server.js(自定义express服务器)

然后,我们可以从_app.js中的document.cookie获取XSRF-TOKEN客户端

\u app.js

function create(initialState, { getToken, cookies, csrfToken }) {
  const httpLink = createHttpLink({
    uri: "http://localhost:3000/graphql",
    credentials: "include"
  });

    const authLink = setContext((_, { headers }) => {
    const token = getToken();
    return {
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : "",
        Cookie: cookies ? cookies : "",
        "x-xsrf-token": csrfToken ? csrfToken : ""
      }
    };
  });
static async getInitialProps(ctx) {
  const {
    Component,
    router,
    ctx: { req, res }
  } = ctx;
  const apollo = initApollo(
    {},
    {
      getToken: () => parseCookies(req).token,
      cookies: req ? req.headers.cookie : "",
      csrfToken: res ? res.locals.csrfToken : document.cookie
    }
  );
import React, { useEffect } from 'react';
import axios from 'axios';
import PropTypes from 'prop-types';
import Head from 'next/head';

export default function MyApp(props) {
  const { Component, pageProps } = props;

  useEffect(() => {
    // Get the XSRF-TOKEN from cookies
    function getCookie(name) {
      const value = `; ${document.cookie}`;
      const parts = value.split(`; ${name}=`);
      if (parts.length === 2) return parts.pop().split(';').shift();
    }

    // set the 'csrf-token' as header on Axios POST requests only (please see csurf docs to see which other headers they accept)
    // you could also add PUT or PATCH if you wish
    axios.defaults.headers.post['csrf-token'] = getCookie('XSRF-TOKEN');

    // The rest of your UseEffect code (if any).....
  }, []);

  // Your app
  return (
    <React.Fragment>
      <Head></Head>
      <Navbar />
      <Component {...pageProps} />
      <Footer />
    </React.Fragment>
  );
}

MyApp.propTypes = {
  Component: PropTypes.elementType.isRequired,
  pageProps: PropTypes.object.isRequired,
};

import React,{useffect}来自“React”;
从“axios”导入axios;
从“道具类型”导入道具类型;
从“下一个/头”导入头;
导出默认函数MyApp(道具){
const{Component,pageProps}=props;
useffect(()=>{
//从cookies获取XSRF-TOKEN
函数getCookie(名称){
常量值=`;${document.cookie}`;
const parts=value.split(`;${name}=`);
如果(parts.length==2)返回parts.pop().split(“;”).shift();
}
//仅在Axios POST请求中将“csrf令牌”设置为标头(请参阅csurf文档以了解它们接受哪些其他标头)
//如果愿意,还可以添加PUT或补丁
axios.defaults.headers.post['csrf-token']=getCookie('XSRF-token');
//剩余的UseEffect代码(如果有)。。。。。
}, []);
//你的应用程序
返回(
);
}
MyApp.propTypes={
组件:PropTypes.elementType.isRequired,
pageProps:PropTypes.object.isRequired,
};
我唯一不确定的是,将令牌传递到前端是否有任何安全隐患?我一直认为CSRF只在服务器端处理。但是在csurf的文档中,他们有一些示例,用于将令牌传递到req正文或标头。也许有更多安全见解的人可以分享他们的专长

因为我们不使用会话,服务器生成两个令牌,一个称为
\u csrf
——这是正常的,因为这是csurf将验证的秘密

注释 如果您以这种方式实现它,并且您正在Postman/Doministance中进行测试,那么csurf中间件将拒绝一个常规的POST请求http://localhost:PORT (在开发中)并从它返回的cookie中获取csrf令牌。这有点烦人,因此您可以在开发模式下删除中间件,并确保在进入生产之前将其添加回


你找到解决办法了吗?我也面临同样的问题。有什么想法吗??