XF 2.0 CORS requests & XF cookies

Lukas W.

Well-known member
we're currently having a client that is attempting to set up his external platform and XenForo installation, where his external page is situated on the main domain xyz.com and XenForo is located on the subdomain forums.xyz.com. We've configured the server to allow CORS requests to be allowed by now, so all scripts, stylesheets, etc. are loaded correctly.

The issue we're currently stuck with is XenForo Ajax requests not passing through, as we constantly receive Cookies are required to use this site. You must accept them to continue using the site. errors. The cookies are currently configured as follows:
Code:
'prefix' => 'xf_',
'path' => '/',
'domain' => '.xyz.com'

we've also already tried to change the domain to xyz.com and forums.xyz.com with the same effect. Cookies seem to be carried over correctly, meaning if we log into XenForo on forums.xyz.com and then switch to the external page on xyz.com, cookies remain set and the user is still logged in.

Any idea why we continue to receive this error on all Ajax calls (such as login dropdown, etc.)?
 
I believe that error would be triggered when the CSRF cookie is not present in a request where it is expected (typically POST requests).
 
Not the token parameter (_xfToken) itself, but the cookie (xf_csrf by default, depending on the cookie prefix). It looks like they are both required for CSRF validation.
 
Cookies are definitely all set and valid. I get them from XF and carry them over to the custom page, but I also get them generated on the custom page if the y don't exist already. We're launching the XF app and all, we're just operating on a different subdomain.
 
Hmm, I'm not sure then. As best I can tell, the only place in the code that would return that error is when CSRF validation fails with the error 'no_cookie'. Just to be sure, you can confirm the cookies are sent in the AJAX requests via the inspector:
screenshot-eA3ID4.webp

Even if they're set in the client, they might not be sent on the request. Barring that, I have no clue :confused:
 
Last edited:
Indeed tracked it down to XF not sending the cookies with ajax requests.

I made sure that XF sends the cookies with the requests with the following script:
Code:
$.ajaxPrefilter( function( options, originalOptions, jqXHR ) {
  options.crossDomain ={
    crossDomain: true
  };
  options.xhrFields = {
    withCredentials: true
  };
});

And the server allows it with the corresponding header rewrite:
Code:
<IfModule mod_headers.c>
   SetEnvIf Origin ^(https?://(?:.+\.)?localhost(?::\d{1,5})?)$   CORS_ALLOW_ORIGIN=$1
   Header set Access-Control-Allow-Origin  %{CORS_ALLOW_ORIGIN}e   env=CORS_ALLOW_ORIGIN
   Header set Access-Control-Allow-Credentials: true
</IfModule>

I'm now running into the security error Security error occurred. Please press back, refresh the page, and try again. caused by the CSRF token apparently not being valid. The token is coming from XF, but I find myself unable to track down why it isn't accepted by it again.
 
Line 194, the actual check against the validator. Apparently the token is wrong, but I don't see a reason why, as it's coming from XF.
 
When you make the AJAX request, does any error appear in the browser console? What if you run the browser with CORS security disabled:

Code:
chrome --disable-web-security --user-data-dir

Do you still experience the same problem? (Don't visit unsafe sites in this mode.)
 
I think we're past the initial CORS and AJAX request difficulties. The request is working fine, and all data is transmitted in order, XF just doesn't accept the CSRF from the request.
 
Line 194, the actual check against the validator. Apparently the token is wrong, but I don't see a reason why, as it's coming from XF.
That indeed sounds like the data is not being sent correctly:
PHP:
                if ($validityPeriod > 0 && ($tokenTime + $validityPeriod) < \XF::$time)
                {
                    $error = 'expired';
                    return false;
                }
We're talking about this line, yea?
 
($csrfValidator($cookie, $tokenTime) === $tokenValue)
It's failing this check specifically.

Printing out the data used at that point for example shows:
Code:
$cookie := nzeOWLM6F6tyEl_F
$tokenTime := 1533423738
$tokenValue := 25b8c4933398a0d588a5b99ea07faf8b
$csrfValidator($cookie, $tokenTime) := 6a51d25f88286ff841e75d6f8c866a71
 
The only thing I can think of is that the cookie used to generate the token is different from the one used to validate the token.

Essentially:
  • The CSRF cookie is captured. If no CSRF cookie is present, a new one is generated and set.
  • The cookie is used along with the timestamp of the request (\XF::$time) and a global salt to generate a token unique to that request.
  • AJAX calls set the _xfToken parameter from the JS config object in the helper_js_global template.
  • CSRF requests are validated by splitting the timestamp and value from _xfToken (comma delimitated), and comparing the value with one generated from the CSRF cookie and timestamp. It should match the value generated in the second step.
I would go through these steps and set breakpoints or dump the output until you find the discrepancy. You can look at the values in the $container['csrf.token'] closure on one request and compare it with the ones in the $container['csrf.validator'] closure on a subsequent request. Either a different cookie (or a different timestamp, but that's pretty unlikely since it's passed with the token) is being used between the time the token is generated on one request and validated on a subsequent request.
 
Last edited:
I think we're past the initial CORS and AJAX request difficulties. The request is working fine, and all data is transmitted in order, XF just doesn't accept the CSRF from the request.

User and session cookies are HTTP only cookies whereas CSRF cookie isn't, I thought there might be a weird browser behavior (related to CORS?) where they were not treated equally. On normal page loads, CSRF cookie isn't a part of the authentication process, so we wouldn't notice if something was wrong with it. You can try manually changing the value of CSRF cookie to something simple like "a", reload the page so the corresponding _xfToken value will be created and then make an AJAX request, checking if the cookie value is correct at csrfValidator, i.e. "a".
 
So I found the source of the error, although I haven't been able to find anyone else with a similar error or a workaround. My ajax request with the credentials appended with the code here does not send the current Cookies from xyz.mypage.com but from the main page mypage.com, which is the xf installation. My guess is that this might be something that could be worked around with the cookie domain setting, but I haven't been able to come up with a workaround that doesn't break stuff
 
The main page is the original XenForo installation, my subdomain is a different system inside an XF wrapper, much like XPress. So yeah, the main page does set cookies as well. Ideally they'd be taken over by my subdomain, but it doesn't really hurt if not, as long as the ajax would send the right ones, but changing the cookie domain in the XF config to mydomain.com does not expose them to the subdomain, and setting it to .mydomain.com breaks it on the main domain.
 
Top Bottom