<p>Thanks for posting your headers, that was very helpful. I experienced a very similar problem just the other day and was able to work it out via trial and error and a whole bunch of Google/StackOverflow searches. I'm hoping I can save you the trouble.</p>
<p>So your .htaccess was not set up to respond to OPTIONS requests, which are sent as a preflight before every request. That can be fixed with the following:</p>
<pre><code>&lt;IfModule mod_rewrite.c&gt;
    RewriteEngine On
    RewriteCond %{REQUEST_METHOD} OPTIONS
    RewriteRule ^(.*)$ $1 [R=200,L]
&lt;/IfModule&gt;
</code></pre>
<p>This just ensures that the server sends an OK response to a preflight request.</p>
<p>Now you have to make sure that your origin domain is actually allowed to make the request you're attempting. Here's how I solved this:</p>
<pre><code>&lt;ifModule mod_headers.c&gt;
    Header always set Access-Control-Allow-Headers &quot;X-Requested-With, Authorization, Content-Type, Request-Method&quot;
    Header always set Access-Control-Allow-Methods &quot;POST, GET, OPTIONS, DELETE, PUT&quot;
    SetEnvIf Origin &quot;^http(s)?://(.+\.)?(localhost|staging.example.com|example.com)(:[0-9]+)?$&quot; origin_is=$0
    Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is
&lt;/ifModule&gt;
</code></pre>
<p>We're updating the headers to allow certain kinds of requests through which would normally be disabled by default. You can adjust these as necessary, for example you may not need to allow Authorization headers or DELETE requests.</p>
<p>Then we whitelist a specified list of origins that should be allowed to make the request in these magic lines:</p>
<pre><code>SetEnvIf Origin &quot;^http(s)?://(.+\.)?(localhost|staging.example.com|example.com)(:[0-9]+)?$&quot; origin_is=$0
Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is
</code></pre>
<p>Update the list of allowed Origins, including a pipe between each. You may be able to wildcard entire domains. What this is doing is saying &quot;if a request is from staging.example.com, set the appropriate Access-Control-Allow-Origin header.&quot;</p>
<hr />
<p>Update from the future: Craft 3.5 includes an <a href="https://craftcms.com/docs/3.x/config/config-settings.html#allowedgraphqlorigins" rel="nofollow noreferrer">allowed GraphQL origins</a> config setting which should presumably make this much simpler. You should be able to leave out both of the origin-related headers in my second example.</p>

CC BY-SA 4.0
2020-07-14T17:26:12.807Z
Oh wow. I believe this has solved it. That's the only feedback I have, sorry!
Clive Portman
CC BY-SA 4.0
2020-07-15T18:41:48.930Z
Just a thought, but I'm having some issues with the login when CSRF protection is enabled. If I collect the CSRF token from the server, then the preflight request is made, will that generate a new CSRF token and prevent the one I've collected from working?
Clive Portman
CC BY-SA 4.0
2020-07-15T19:37:33.300Z
Yeah, I have the same problem, haven't figured that out yet! But I have a temporary solution that I'll share on the other question you asked.
Dalton Rooney
CC BY-SA 4.0
2020-07-15T19:40:34.070Z
Thanks. Hopefully it's not disabling CSRF...
Clive Portman
CC BY-SA 4.0
2020-08-27T20:53:22.510Z
Hi @DaltonRooney I will continue the conversation here. I tried to follow your solution which makes absolutely sense. The weird thing is that the request goes through although I have removed your htaccess code (resulting in an entry within my form database in the backend). Still the browser would show me the CORS error. When inspecting the network tab I see that the request gets a sucessful response.So why is the browser telling me that CORS is an issue although the data is being sent and even the response is available in the network tab (but only there. AJAX does not receive it).. any idea 
Merc
CC BY-SA 4.0
2020-08-27T21:07:30.663Z
or am I using the wrong .htaccess? I placed it in /web, which should be correct right? I only added the part about the Headers and I also tried setting a wildcard `Header always set Access-Control-Allow-Origin "*"`. Nothing helped. My browser tells me: `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cms.localdomain.test/index.php?p=actions/sprout-forms/entries/save-entry. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).`
Merc
CC BY-SA 4.0
2020-07-14T16:35:30.863Z
2020-08-13T17:35:46.540Z
2020-08-13T17:35:46.540Z
Dalton Rooney
<p>I've built an AJAX frontend login form, using plain Javascript. It works fine until I try and access it from a different domain.</p>
<p>The problem is CORS and the preflight request. The preflight request doesn't contain the payload from the login form so Craft routes it to a template called 'users/login'. But as the template doesn't exist, the preflight test is returned 'not ok'. CORS looks to be setup okay on the server, because I can get around the problem by creating an empty template for users/login. Then the login form works (preflight test returns ok, so browser proceeds with main request).</p>
<p>What I really need is for the preflight check to not trigger a 404 and return ok, even without the proper payload. Which means Craft not routing that to a template.</p>
<p>Console message without template existing:</p>
<pre><code>Access to XMLHttpRequest at 'http://server.localhost/users/login' from origin 'http://test.localhost' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
</code></pre>
<p>General (under headers tab in Chrome):</p>
<pre><code>Request URL: http://server.localhost/users/login
Request Method: OPTIONS
Status Code: 404 Not Found
Remote Address: [::1]:80
Referrer Policy: origin-when-cross-origin
</code></pre>
<p>Response header:</p>
<pre><code>Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: x-Requested-With, Content-Type, origin, authorization, accept, client-security-token
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT
Access-Control-Allow-Origin: http://test.localhost
Access-Control-Expose-Headers: Content-Security-Policy, Location
Access-Control-Max-Age: 600
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Date: Tue, 14 Jul 2020 05:49:33 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.41 (Win64) PHP/7.0.33
Transfer-Encoding: chunked
X-Powered-By: Craft CMS
</code></pre>
<p>I'm using Wampserver and adding those via .htaccess.</p>

CC BY-SA 4.0
2020-07-13T23:05:01.107Z
Is there a response header that you could include in your description?
Dalton Rooney
CC BY-SA 4.0
2020-07-14T12:27:38.340Z
Try adding this rewrite rule to you .htaccess:     ```RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]```
Dalton Rooney
CC BY-SA 4.0
2020-07-14T12:40:34.973Z
500 Internal Server Error. No, scrap that. I put it on one line and moved it to two lines and its fine. But I'm back to ```Access to XMLHttpRequest at 'http://server.localhost/users/login' from origin 'http://test.localhost' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.``` So it's made things worse. I have the template in there at the moment and it goes through fine until I add your line above.
Clive Portman
CC BY-SA 4.0
2020-07-14T16:18:56.263Z
Sorry everything was on one line, I was trying to comment from my phone! The new message was actually very helpful, I’ll try posting an answer with some more info.
Dalton Rooney
CC BY-SA 4.0
2020-07-14T18:40:26.420Z
Hi, I’ve seen you’ve got an answer (which is also super helpful to me). I’ve played with logging in across domains and just wondered how you retain/use that session on the other domain?
dmatthams
CC BY-SA 4.0
2020-07-15T07:59:05.850Z
I haven't got that far yet. I'm now working on CSRF and getting that working with the other domain. The plan is I use JWT (there is a Craft plugin I'll be using to create it) but I want to sort out the CSRF thing first.
Clive Portman
<login><routing>
CORS preflight check failing for AJAX login form on different domain
# CORS preflight check failing for AJAX login form on different domain

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

I've built an AJAX frontend login form, using plain Javascript. It works fine until I try and access it from a different domain.
The problem is CORS and the preflight request. The preflight request doesn't contain the payload from the login form so Craft routes it to a template called 'users/login'. But as the template doesn't exist, the preflight test is returned 'not ok'. CORS looks to be setup okay on the server, because I can get around the problem by creating an empty template for users/login. Then the login form works (preflight test returns ok, so browser proceeds with main request).
What I really need is for the preflight check to not trigger a 404 and return ok, even without the proper payload. Which means Craft not routing that to a template.
Console message without template existing:
Access to XMLHttpRequest at 'http://server.localhost/users/login' from origin 'http://test.localhost' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

General (under headers tab in Chrome):
Request URL: http://server.localhost/users/login
Request Method: OPTIONS
Status Code: 404 Not Found
Remote Address: [::1]:80
Referrer Policy: origin-when-cross-origin

Response header:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: x-Requested-With, Content-Type, origin, authorization, accept, client-security-token
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT
Access-Control-Allow-Origin: http://test.localhost
Access-Control-Expose-Headers: Content-Security-Policy, Location
Access-Control-Max-Age: 600
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8
Date: Tue, 14 Jul 2020 05:49:33 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.41 (Win64) PHP/7.0.33
Transfer-Encoding: chunked
X-Powered-By: Craft CMS

I'm using Wampserver and adding those via .htaccess.

**Tags:** login, routing

> **Comments:**
> **username_1**: Is there a response header that you could include in your description?  
> **username_1**: Try adding this rewrite rule to you .htaccess:     ```RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=200,L]```  
> **username_0**: 500 Internal Server Error. No, scrap that. I put it on one line and moved it to two lines and its fine. But I'm back to ```Access to XMLHttpRequest at 'http://server.localhost/users/login' from origin 'http://test.localhost' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.``` So it's made things worse. I have the template in there at the moment and it goes through fine until I add your line above.  
> **username_1**: Sorry everything was on one line, I was trying to comment from my phone! The new message was actually very helpful, I’ll try posting an answer with some more info.  
> **username_2**: Hi, I’ve seen you’ve got an answer (which is also super helpful to me). I’ve played with logging in across domains and just wondered how you retain/use that session on the other domain?  
> **username_0**: I haven't got that far yet. I'm now working on CSRF and getting that working with the other domain. The plan is I use JWT (there is a Craft plugin I'll be using to create it) but I want to sort out the CSRF thing first.  

---

## 1 answers

### Answer by username_1 on 2020-07-14. Score: 3 | Accepted answer

Thanks for posting your headers, that was very helpful. I experienced a very similar problem just the other day and was able to work it out via trial and error and a whole bunch of Google/StackOverflow searches. I'm hoping I can save you the trouble.
So your .htaccess was not set up to respond to OPTIONS requests, which are sent as a preflight before every request. That can be fixed with the following:

    RewriteEngine On
    RewriteCond %{REQUEST_METHOD} OPTIONS
    RewriteRule ^(.*)$ $1 [R=200,L]


This just ensures that the server sends an OK response to a preflight request.
Now you have to make sure that your origin domain is actually allowed to make the request you're attempting. Here's how I solved this:

    Header always set Access-Control-Allow-Headers "X-Requested-With, Authorization, Content-Type, Request-Method"
    Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
    SetEnvIf Origin "^http(s)?://(.+\.)?(localhost|staging.example.com|example.com)(:[0-9]+)?$" origin_is=$0
    Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is


We're updating the headers to allow certain kinds of requests through which would normally be disabled by default. You can adjust these as necessary, for example you may not need to allow Authorization headers or DELETE requests.
Then we whitelist a specified list of origins that should be allowed to make the request in these magic lines:
SetEnvIf Origin "^http(s)?://(.+\.)?(localhost|staging.example.com|example.com)(:[0-9]+)?$" origin_is=$0
Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is

Update the list of allowed Origins, including a pipe between each. You may be able to wildcard entire domains. What this is doing is saying "if a request is from staging.example.com, set the appropriate Access-Control-Allow-Origin header."

Update from the future: Craft 3.5 includes an allowed GraphQL origins config setting which should presumably make this much simpler. You should be able to leave out both of the origin-related headers in my second example.

> **Comments:**
> **username_0**: Oh wow. I believe this has solved it. That's the only feedback I have, sorry!  
> **username_0**: Just a thought, but I'm having some issues with the login when CSRF protection is enabled. If I collect the CSRF token from the server, then the preflight request is made, will that generate a new CSRF token and prevent the one I've collected from working?  
> **username_1**: Yeah, I have the same problem, haven't figured that out yet! But I have a temporary solution that I'll share on the other question you asked.  
> **username_0**: Thanks. Hopefully it's not disabling CSRF...  
> **username_3**: Hi @DaltonRooney I will continue the conversation here. I tried to follow your solution which makes absolutely sense. The weird thing is that the request goes through although I have removed your htaccess code (resulting in an entry within my form database in the backend). Still the browser would show me the CORS error. When inspecting the network tab I see that the request gets a sucessful response.So why is the browser telling me that CORS is an issue although the data is being sent and even the response is available in the network tab (but only there. AJAX does not receive it).. any idea   
> **username_3**: or am I using the wrong .htaccess? I placed it in /web, which should be correct right? I only added the part about the Headers and I also tried setting a wildcard `Header always set Access-Control-Allow-Origin "*"`. Nothing helped. My browser tells me: `Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://cms.localdomain.test/index.php?p=actions/sprout-forms/entries/save-entry. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).`  

---

