<p>I don't have a good answer for this, but my response is too long to to put into a comment so here goes. I've been experiencing the same thing — I can successfully retrieve the CSRF token, but when I try to log in I get the dreaded 400 Bad Request error.</p>
<p>I found this tidbit on a page about <a href="https://www.yiiframework.com/wiki/274/how-to-validate-csrf-token-with-session" rel="nofollow noreferrer">Yii CSRF protection</a>:</p>
<blockquote>
<p>When you ebable CSRF validation and use form builder to generate a
form(only post), Yii will auto generate a hidden field and put it in
the form, at the same time, Yii will create a cookie with CSRF token.
When you submit the form, Yii will compare two CSRF tokens from post
and cookie.</p>
</blockquote>
<p>I know for a fact that I don't have a cross-domain cookie so there's nothing to compare to. I think the missing cookie is the problem.</p>
<p>In any case, searching for Yii CSRF has given me more to work with than sticking to the Craft documentation. Surely someone has figured this out. In the meantime I'm disabling CSRF from whitelisted domains in my Craft config like this: (for development only):</p>
<pre><code>'enableCsrfProtection' =&gt; (
  $_SERVER['HTTP_ORIGIN']  != 'http://localhost:3000' &amp;&amp; 
  $_SERVER['HTTP_ORIGIN']  != 'https://staging.example.com' &amp;&amp; 
  $_SERVER['HTTP_ORIGIN']  != 'https://example.com'
),
</code></pre>
<p><strong>Quick update</strong>: maybe <a href="https://github.com/yiisoft/yii2/issues/10809" rel="nofollow noreferrer">this thread can be of help</a>.</p>

CC BY-SA 4.0
2020-07-15T19:56:19.097Z
Thanks for sharing. I'm also wondering if there's any possibilities with this: https://github.com/craftcms/cms/issues/5303
Clive Portman
CC BY-SA 4.0
2020-07-15T20:32:00.030Z
And this is interesting: https://softwareengineering.stackexchange.com/questions/141019/should-cookies-be-used-in-a-restful-api#:~:text=Cookies%20if%20used%20to%20maintain,%2D%20which%20isn't%20restful.
Clive Portman
CC BY-SA 4.0
2020-07-15T21:08:47.003Z
Something else: the talk detailing the creation of a REST API at 8:15 disables CSRF. At least I think that's what they mean.
Clive Portman
CC BY-SA 4.0
2020-07-15T19:50:24.733Z
2020-07-15T19:50:24.733Z
Dalton Rooney
<p>Having sorted out the CORS issue and succesfully logging into Craft from a different domain using Javascript, I've now turned CSRF protection back on. I've got a module returning the CSRF token name and value fine. I fetch the CSRF token with a GET request, then use that token in my login form submission. But I cannot get past Craft being unable to verify the data submission. The same code works fine when run from the same domain.</p>
<p>Does anyone have any experience of doing this?</p>
<pre><code>function fetchCSRFToken() {

    var url = 'http://server.localhost/actions/server-module/server/get-csrf';

    function status(response) {
      if (response.status &gt;= 200 &amp;&amp; response.status &lt; 300) {
        return Promise.resolve(response)
      } else {
        return Promise.reject(new Error(response.statusText))
      }
    }

    function json(response) {
      return response.json()
    }

    return fetch(url)
        .then(status)
        .then(json)
        .then(function(tokenData) {
            return tokenData;
        }).catch(function(error) {
        });

}

function login(event, tokenData) {

    var rawAction = &quot;http://server.localhost/&quot; + event.target.action.value;
    var action = encodeURIComponent(event.target.action.value);
    var csrfTokenName = encodeURIComponent(tokenData.name);
    var csrfTokenValue = encodeURIComponent(tokenData.value);
    var loginName = encodeURIComponent(event.target.loginName.value);
    var password = encodeURIComponent(event.target.password.value);

    var data = &quot;action=&quot;+action+&quot;&amp;&quot;+csrfTokenName+&quot;=&quot;+csrfTokenValue+&quot;&amp;loginName=&quot;+loginName+&quot;&amp;password=&quot;+password;

    var ajax=new XMLHttpRequest();
    ajax.onreadystatechange = function(){
        if(this.readyState == 4 &amp;&amp; this.status ==200){
            console.log(this.responseText);
        }

    }
          
    ajax.open('POST', rawAction, true);
    ajax.setRequestHeader('Accept', 'application/json, text/javascript, */*; q=0.01');
    ajax.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    ajax.setRequestHeader(&quot;Content-Type&quot;,&quot;application/x-www-form-urlencoded; charset=UTF-8&quot;);

    ajax.send(data);

}


document.querySelector('#loginform-js').addEventListener('submit', function(event) {

    event.preventDefault();

    fetchCSRFToken().then(function(token) {
        login(event, token);
    });

});
</code></pre>
<p>And how I get the CSRF token from a module controller:</p>
<pre><code>    /**
     * @return Response
     */
    public function actionGetCsrf(): Response
    {
        return $this-&gt;asJson([
            'name' =&gt; Craft::$app-&gt;getConfig()-&gt;getGeneral()-&gt;csrfTokenName,
            'value' =&gt; Craft::$app-&gt;getRequest()-&gt;getCsrfToken(),
        ]);
    }
</code></pre>
<p>EDIT: I suspect the preflight CORS request is invalidating the CSRF token? The preflight is only necessary on the remote server.</p>

CC BY-SA 4.0
2020-07-16T14:58:25.797Z
Just curious, what's the use case of logging in from a different domain? Wouldn't setting up another Craft site work here so it's actually under the same domain?
RitterKnight
CC BY-SA 4.0
2020-07-27T12:36:24.473Z
@RitterKnight The main site handles the commerce side of things, where customers create their account. Then there is a customer portal on a different domain. It's been a real headache managing it through one installation with all the templates. I'm rebuilding the customer portal using Svelte, on a separate server.
Clive Portman
CC BY-SA 4.0
2020-07-27T16:11:00.133Z
Would a webhook work for your use case? When a user registers or changes their password, it basically gets picked up by Commerce.  Not sure how often people “flip” back and forth between portal or the site but at least a user only has to manage one password. Technically both systems would handle their own auth.
RitterKnight
CC BY-SA 4.0
2020-07-27T21:46:30.903Z
Further purchases can be made within the portal, as well as masses of user fields which need updating all the time.  I don't want two installations of Craft - is that what you're suggesting?
Clive Portman
CC BY-SA 4.0
2020-07-29T15:07:56.020Z
Gotcha, so everything is in Craft then? Are these 2 separate "sites" inside Craft or just one? How are you dealing with different domains exactly? If it's just one, wouldn't users be logging into the same instance of Craft?  If you need to have separate layouts for each for example, there's ways to change up the login form instead of dealing with separate domains... not sure if that could work for your use case.
RitterKnight
CC BY-SA 4.0
2020-07-29T15:16:05.883Z
Yup. Everything is in Craft. They register and purchase a main product on the first site. That gives them access to functionality within the portal. The portal is where they manage their data but they can also make additional purchases in there as well. At the moment, you can access the site through both domains on the same server. But I want to re-build the portal on a different server and handle everything through Javascript (Svelte/Sapper). I understand this is where Craft is headed, it's just not there yet.
Clive Portman
CC BY-SA 4.0
2020-07-29T16:49:04.170Z
When Craft is setting the CSRF token on the other domain, it's expecting the referrer to also be that same domain IIRC. Just create a special form on the other domain that  *looks* like the other site. Instead of logging in at ``firstdomain.com/login`` you login at ``seconddomain.com/whatever``. Technically you'd be spoofing yourself but the CSRF token would pass because you control it and it's coming from the right server.  The other option would be to turn off CSRF protection at a specific endpoint.
RitterKnight
<javascript><csrf><headless>
JS login form on different domain and handling the CSRF token
# JS login form on different domain and handling the CSRF token

(Asked by: username_0 on 2020-07-15)

Having sorted out the CORS issue and succesfully logging into Craft from a different domain using Javascript, I've now turned CSRF protection back on. I've got a module returning the CSRF token name and value fine. I fetch the CSRF token with a GET request, then use that token in my login form submission. But I cannot get past Craft being unable to verify the data submission. The same code works fine when run from the same domain.
Does anyone have any experience of doing this?
function fetchCSRFToken() {

    var url = 'http://server.localhost/actions/server-module/server/get-csrf';

    function status(response) {
      if (response.status >= 200 && response.status < 300) {
        return Promise.resolve(response)
      } else {
        return Promise.reject(new Error(response.statusText))
      }
    }

    function json(response) {
      return response.json()
    }

    return fetch(url)
        .then(status)
        .then(json)
        .then(function(tokenData) {
            return tokenData;
        }).catch(function(error) {
        });

}

function login(event, tokenData) {

    var rawAction = "http://server.localhost/" + event.target.action.value;
    var action = encodeURIComponent(event.target.action.value);
    var csrfTokenName = encodeURIComponent(tokenData.name);
    var csrfTokenValue = encodeURIComponent(tokenData.value);
    var loginName = encodeURIComponent(event.target.loginName.value);
    var password = encodeURIComponent(event.target.password.value);

    var data = "action="+action+"&"+csrfTokenName+"="+csrfTokenValue+"&loginName="+loginName+"&password="+password;

    var ajax=new XMLHttpRequest();
    ajax.onreadystatechange = function(){
        if(this.readyState == 4 && this.status ==200){
            console.log(this.responseText);
        }

    }
          
    ajax.open('POST', rawAction, true);
    ajax.setRequestHeader('Accept', 'application/json, text/javascript, */*; q=0.01');
    ajax.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
    ajax.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");

    ajax.send(data);

}


document.querySelector('#loginform-js').addEventListener('submit', function(event) {

    event.preventDefault();

    fetchCSRFToken().then(function(token) {
        login(event, token);
    });

});

And how I get the CSRF token from a module controller:
    /**
     * @return Response
     */
    public function actionGetCsrf(): Response
    {
        return $this->asJson([
            'name' => Craft::$app->getConfig()->getGeneral()->csrfTokenName,
            'value' => Craft::$app->getRequest()->getCsrfToken(),
        ]);
    }

EDIT: I suspect the preflight CORS request is invalidating the CSRF token? The preflight is only necessary on the remote server.

**Tags:** javascript, csrf, headless

> **Comments:**
> **username_1**: Just curious, what's the use case of logging in from a different domain? Wouldn't setting up another Craft site work here so it's actually under the same domain?  
> **username_0**: @username_1 The main site handles the commerce side of things, where customers create their account. Then there is a customer portal on a different domain. It's been a real headache managing it through one installation with all the templates. I'm rebuilding the customer portal using Svelte, on a separate server.  
> **username_1**: Would a webhook work for your use case? When a user registers or changes their password, it basically gets picked up by Commerce.  Not sure how often people “flip” back and forth between portal or the site but at least a user only has to manage one password. Technically both systems would handle their own auth.  
> **username_0**: Further purchases can be made within the portal, as well as masses of user fields which need updating all the time.  I don't want two installations of Craft - is that what you're suggesting?  
> **username_1**: Gotcha, so everything is in Craft then? Are these 2 separate "sites" inside Craft or just one? How are you dealing with different domains exactly? If it's just one, wouldn't users be logging into the same instance of Craft?  If you need to have separate layouts for each for example, there's ways to change up the login form instead of dealing with separate domains... not sure if that could work for your use case.  
> **username_0**: Yup. Everything is in Craft. They register and purchase a main product on the first site. That gives them access to functionality within the portal. The portal is where they manage their data but they can also make additional purchases in there as well. At the moment, you can access the site through both domains on the same server. But I want to re-build the portal on a different server and handle everything through Javascript (Svelte/Sapper). I understand this is where Craft is headed, it's just not there yet.  
> **username_1**: When Craft is setting the CSRF token on the other domain, it's expecting the referrer to also be that same domain IIRC. Just create a special form on the other domain that  *looks* like the other site. Instead of logging in at ``firstdomain.com/login`` you login at ``seconddomain.com/whatever``. Technically you'd be spoofing yourself but the CSRF token would pass because you control it and it's coming from the right server.  The other option would be to turn off CSRF protection at a specific endpoint.  

---

## 1 answers

### Answer by username_2 on 2020-07-15. Score: 2

I don't have a good answer for this, but my response is too long to to put into a comment so here goes. I've been experiencing the same thing — I can successfully retrieve the CSRF token, but when I try to log in I get the dreaded 400 Bad Request error.
I found this tidbit on a page about Yii CSRF protection:

When you ebable CSRF validation and use form builder to generate a
form(only post), Yii will auto generate a hidden field and put it in
the form, at the same time, Yii will create a cookie with CSRF token.
When you submit the form, Yii will compare two CSRF tokens from post
and cookie.

I know for a fact that I don't have a cross-domain cookie so there's nothing to compare to. I think the missing cookie is the problem.
In any case, searching for Yii CSRF has given me more to work with than sticking to the Craft documentation. Surely someone has figured this out. In the meantime I'm disabling CSRF from whitelisted domains in my Craft config like this: (for development only):
'enableCsrfProtection' => (
  $_SERVER['HTTP_ORIGIN']  != 'http://localhost:3000' && 
  $_SERVER['HTTP_ORIGIN']  != 'https://staging.example.com' && 
  $_SERVER['HTTP_ORIGIN']  != 'https://example.com'
),

Quick update: maybe this thread can be of help.

> **Comments:**
> **username_0**: Thanks for sharing. I'm also wondering if there's any possibilities with this: https://github.com/craftcms/cms/issues/5303  
> **username_0**: And this is interesting: https://softwareengineering.stackexchange.com/questions/141019/should-cookies-be-used-in-a-restful-api#:~:text=Cookies%20if%20used%20to%20maintain,%2D%20which%20isn't%20restful.  
> **username_0**: Something else: the talk detailing the creation of a REST API at 8:15 disables CSRF. At least I think that's what they mean.  

---

