Handle CSRF attack

how can prevent CSRF attack in moqui when using session and cookie to handle login

@marwand Do you remember how we got around this for Vue Storefront?

@michael I’m assuming the question “how can prevent CSRF attack in moqui when using session and cookie to handle login” means how do we use the X-CSRF-Token with moqui sessions. Please correct me if I misunderstood.

Vuestorefront was a bit of a special case, since the express server was a middleware between moqui and the client during SSR. Syncing the session cookie + csrf tokens between the client and express server was what made it challenging.

In more common cases, where you just have a client (Vue/React/JS/etc…) talking to the moqui server, here’s how I handle it. In this example, I use axios as my http client, and add interceptors that run before each request, and after each response.

Cookies are handled by axios automatically, make sure to use the property withCredentials: true, when creating the axios instance.

Another thing to note, is that I’ve found the value of the X-CSRF-Token to be always equal to moquiSessionToken, and you’ll see I use the same value for both in the code below.

// The purpose of this is before any request, if we have a session token
// stored in our store/local storage, add it to the request before performing it.
axios.interceptors.request.use((config: any) => {
    // I store the session token in a persisted store (pinia in this example), 
    // but could be redux/any others. 
    // You could also store it in local/session storage
    const userStore = useUserStore(store);
    const moquiSessionToken = userStore.moquiSessionToken;

    if (moquiSessionToken && !config.headers?.moquisessiontoken) {
      config.headers.moquisessiontoken = moquiSessionToken;
      config.headers['X-CSRF-Token'] = moquiSessionToken;
      config.headers['Content-Type'] = 'application/json';
    }
    return config;
  });

  // The purpose of this is that after any response, if we have a session token
  // in the header, store it in our store/local storage.
  axios.interceptors.response.use(
    //runs on response codes < 400
    function (response) {
      const userStore = useUserStore(store);
      if (response?.headers?.['moquisessiontoken'])
        userStore.setMoquiSessionToken(response.headers?.['moquisessiontoken']);

      //Do anything with the response, if needed (notifications/messages, etc..) 

      return response;
    },
    // Runs on response codes >= 400
    // This is important, as we check for unauthorized status codes, which means we have to log the 
       user out
    function (error) {
      const userStore = useUserStore(store);
      if (error.response?.headers?.['moquisessiontoken'])
        userStore.setMoquiSessionToken(
          error.response.headers?.['moquisessiontoken']
        );
     
      if (error.response) {
        const { status } = error.response;
        if (
          status === StatusCodes.UNAUTHORIZED ||
          status === StatusCodes.FORBIDDEN
        ) {
          //Notify.create({
          //  message: i18n.global.t('Your session has expired, please log in.'),
          //});
          // The logout function resets the user state and clears moquiSessionToken 
          // For example, I set the following variables in my user store
          // isAuthenticated= false,
          // moquiSessionToken='',
          userStore.logout();
          router.push('/login');
        } else {
          Notify.create({
            message: error.response?.data.errors || error.message,
            type: 'negative',
            position: 'bottom',
            timeout: 10000,
          });
        }
      }
      // Do something with response error
      return Promise.reject(error);
    }
  );

Hope this helps!

Yes this is what I assumed also.

That’s great! Thanks for doing that!