Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-07-14T17:26:12.807Z
Answers.Text: Oh wow. I believe this has solved it. That's the only feedback I have, sorry!
Answers.UserDisplayName: Clive Portman
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-07-15T18:41:48.930Z
Answers.Text: 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?
Answers.UserDisplayName: Clive Portman
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-07-15T19:37:33.300Z
Answers.Text: 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.
Answers.UserDisplayName: Dalton Rooney
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-07-15T19:40:34.070Z
Answers.Text: Thanks. Hopefully it's not disabling CSRF...
Answers.UserDisplayName: Clive Portman
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-08-27T20:53:22.510Z
Answers.Text: 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 
Answers.UserDisplayName: Merc
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-08-27T21:07:30.663Z
Answers.Text: 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).`
Answers.UserDisplayName: Merc
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-07-14T16:35:30.863Z
Answers.LastActivityDate: 2020-08-13T17:35:46.540Z
Answers.LastEditDate: 2020-08-13T17:35:46.540Z
Answers.UserDisplayName: Dalton Rooney
Body: <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>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-07-13T23:05:01.107Z
Comments.Text: Is there a response header that you could include in your description?
Comments.UserDisplayName: Dalton Rooney
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-07-14T12:27:38.340Z
Comments.Text: Try adding this rewrite rule to you .htaccess:     ```RewriteCond %{REQUEST_METHOD} OPTIONS
  RewriteRule ^(.*)$ $1 [R=200,L]```
Comments.UserDisplayName: Dalton Rooney
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-07-14T12:40:34.973Z
Comments.Text: 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.
Comments.UserDisplayName: Clive Portman
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-07-14T16:18:56.263Z
Comments.Text: 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.
Comments.UserDisplayName: Dalton Rooney
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-07-14T18:40:26.420Z
Comments.Text: 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?
Comments.UserDisplayName: dmatthams
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2020-07-15T07:59:05.850Z
Comments.Text: 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.
Comments.UserDisplayName: Clive Portman
Tags: <login><routing>
Title: CORS preflight check failing for AJAX login form on different domain
ThreadText: # 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:
  
  ```
  <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteCond %{REQUEST_METHOD} OPTIONS
      RewriteRule ^(.*)$ $1 [R=200,L]
  </IfModule>
  
  ```
  
  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:
  
  ```
  <ifModule mod_headers.c>
      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
  </ifModule>
  
  ```
  
  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](https://craftcms.com/docs/3.x/config/config-settings.html#allowedgraphqlorigins) 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 @username_1 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).`  
  
  ---
  
  