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.

  • Is there a response header that you could include in your description? Jul 13, 2020 at 23:05
  • Thanks. I've added the response header. Jul 14, 2020 at 5:55
  • Try adding this rewrite rule to you .htaccess: RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule ^(.*)$ $1 [R=200,L] Jul 14, 2020 at 12:27
  • 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. Jul 14, 2020 at 12:40
  • 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. Jul 14, 2020 at 16:18

1 Answer 1


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
    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:

<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

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.

  • Oh wow. I believe this has solved it. That's the only feedback I have, sorry! Jul 14, 2020 at 17:26
  • 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? Jul 15, 2020 at 18:41
  • 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. Jul 15, 2020 at 19:37
  • Thanks. Hopefully it's not disabling CSRF... Jul 15, 2020 at 19:40
  • 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
    Aug 27, 2020 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.