1

背景

我有一个安静的后端,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 存储中的令牌
  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

如果我在第 2 步之前添加延迟window.setTimeout,cookie 仍然没有发送,所以我认为浏览器没有足够的时间保存 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);
            });
    });
};
4

1 回答 1

1

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

于 2016-06-13T15:27:38.777 回答