后续xhr请求中不提供httponly cookie

时间:2016-06-11 18:01:26

标签: xmlhttprequest xss csrf redux axios

背景

我有一个安静的后端,React + Redux前端,我试图防止CSRF和XSS攻击。

前端从API请求CSRF令牌。 API响应在HttpOnly cookie和响应正文中设置CSRF令牌。 redux reducer将令牌(从响应主体)保存到redux存储。

如果我在主容器componentDidMount()中请求令牌,一切正常,但关注的是这是一次性的。相反,当对API的请求通过自定义中间件时,如果它不在本地存在,我宁愿中间件请求CSRF令牌。

问题

流程如下(在Chrome 50和Firefox 47上测试):

  1. 请求了CSRF令牌。令牌存储在HttpOnly cookie和redux store
  2. 请求X-CSRF-Token标头设置的原始API调用。 未发送Cookie
  3. 由于缺少Cookie而从API接收403。 API使用新的HttpOnly cookie 进行响应。 Javascript无法看到此Cookie,因此redux商店不会更新。
  4. 使用步骤2中的X-CSRF-Token标头和步骤3中的cookie请求的其他API调用。
  5. 由于Cookie与X-CSRF-Token不匹配而收到403
  6. 如果我在使用window.setTimeout的第2步之前添加了延迟,则Cookie仍然不会被发送,因此我认为它与浏览器的竞争条件<\ n>没有足够的时间保存cookie?

    行动创作者

    const login = (credentials) => {
    
        return {
            type: AUTH_LOGIN,
            payload: {
                api: {
                    method: 'POST',
                    url: api.v1.auth.login,
                    data: credentials
                }
            }
        };
    };
    

    中间件

    /**
     * Ensure the crumb and JWT authentication token are wrapped in all requests to the API.
     */
    export default (store) => (next) => (action) => {
    
        if (action.payload && action.payload.api) {
    
            store.dispatch({ type: `${action.type}_${PENDING}` });
            return ensureCrumb(store)
                .then((crumb) => {
    
                    const state = store.getState();
                    const requestConfig = {
                        ...action.payload.api,
                        withCredentials: true,
                        xsrfCookieName: 'crumb',
                        xsrfHeaderName: 'X-CSRF-Token',
                        headers: {
                            'X-CSRF-Token': crumb
                        }
                    };
    
                    if (state.auth.token) {
                        requestConfig.headers = { ...requestConfig.headers, Authorization: `Bearer ${state.auth.token}` };
                    }
    
                    return axios(requestConfig);
                })
                .then((response) => store.dispatch({ type:`${action.type}_${SUCCESS}`, payload: response.data }))
                .catch((response) => store.dispatch({ type: `${action.type}_${FAILURE}`, payload: response.data }));
        }
    
        return next(action);
    };
    
    /**
     * Return the crumb if it exists, otherwise requests a crumb
     * @param store - The current redux store
     * @returns Promise - crumb token
     */
    const ensureCrumb = (store) => {
    
        const state = store.getState();
        return new Promise((resolve, reject) => {
    
            if (state.crumb.token) {
                return resolve(state.crumb.token);
            }
    
            store.dispatch({ type: CRUMB_PENDING });
            axios.get(api.v1.crumb)
                .then((response) => {
    
                    store.dispatch({ type: CRUMB_SUCCESS, payload: { token: response.data.crumb } });
                    window.setTimeout(() => resolve(response.data.crumb), 10000);
                    // return resolve(response.data.crumb);
                })
                .catch((error) => {
    
                    store.dispatch({ type: CRUMB_FAILURE });
                    return reject(error);
                });
        });
    };
    

1 个答案:

答案 0 :(得分:1)

这是因为我在每个请求上创建了一个新的axios客户端,如果我为所有API请求重复使用相同的axios客户端,则cookie会正确保存并在后续请求中使用。