craftcms / cms

Build bespoke content experiences with Craft.
https://craftcms.com
Other
3.22k stars 626 forks source link

[4.x]: CORS Preflight not HTTP ok status for CP on different domain. #14612

Closed cballenar closed 6 months ago

cballenar commented 6 months ago

What happened?

Description

I started running into this issue after upgrading from 4.5.14 to 4.8.3.

I have a site serving the CP from a different domain, no CP trigger (issues such as assets, cors, iframe had been handled). It's worth mentioning that once the issue begun I also tried changing to using a CP trigger but all the routes still point to the main URL and therefore the issue continued to occur.

After updating I started to receive the following message:

Access to XMLHttpRequest at 'https://domain.com/index.php?p=actions%2Fusers%2Flogin&v=000000000' from origin 'https://craft.domain.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

By troubleshooting, I was able to confirm most/all CORS headers are set correctly (again, they worked before) as the message would change. The message above seems to be the closest to passing but i'm stuck beyond this point.

These are the headers I'm using which are mostly a collection of all possible things. Variations to the URL format would fail:

Access-Control-Allow-Origin: https://craft.domain.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: HEAD,DELETE,POST,GET,OPTIONS,PUT,PATCH
Access-Control-Allow-Headers: Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range

Were there any recent changes to the expected headers/policies? It's very weird that this would happen after an update.

Expected behavior

Supplied CORS configuration should allow the use of the Craft CP Panel from a different domain.

Craft CMS version

4.8.3

PHP version

No response

Operating system and version

No response

Database type and version

No response

Image driver and version

No response

Installed plugins and versions

No response

cballenar commented 6 months ago

While researching, I found the following answer in a different scenario:

The CORS contract requires that authentication not be required on the pre-flight OPTIONS request. It looks like your back-end is requiring authentication on the OPTIONS request and the GET.

Which sounds like something possibly related specially if anything in the code changed. Could this be it?

ddnetters commented 6 months ago

I ran into a similar problem last week. This solved it: https://github.com/craftcms/cms/issues/14440#issuecomment-1985717994. It occurs after 4.8.1 because of this commit using the @web alias https://github.com/craftcms/cms/commit/965c1a6ec9288d1f319732edc0fd26c78feb088d.

brandonkelly commented 6 months ago

Yeah, as of 4.8.1, we are building action URLs using the @web alias, which means the alias needs to be set correctly. If you’re overriding it, make sure it’s set to the exact same base path that Craft is being accessed from (up to but not including index.php).

cballenar commented 6 months ago

Thanks both for the feedback! Could you confirm if this is what you mean by setting it dynamically?

'@web' => $_SERVER['HTTP_HOST'] === 'craft.domain.com' ? App::env('BASE_CP_URL') : getenv('PRIMARY_SITE_URL'),

This is working locally without any issues (so far), I'm just not sure I took the most efficient approach.

I also attempted to solve via the other recommendation. So I tried NOT setting the @web alias and using the default cpTrigger. I thought this would resolve the domain issues but it didn't. Is there anything else I should be checking for?

brandonkelly commented 6 months ago

@cballenar That’s probably correct, but I couldn’t say for sure without knowing all of the base URLs Craft needs to be accessed by, and what those environment variables are set to. Again, it just needs to be set to whatever URL you’re accessing Craft with, up to but not including index.php. So if your index.php file lives at https://craft.domain.com/index.php, it should be https://craft.domain.com/, etc.

So I tried NOT setting the @web alias and using the default cpTrigger. I thought this would resolve the domain issues but it didn't. Is there anything else I should be checking for?

If not setting @web is still not working, then there’s probably a server misconfiguration issue. Go to UtilitiesPHP Info, and verify the following values look correct:

cballenar commented 6 months ago

Thanks a ton for the patience @brandonkelly !

The problem with the alias failing turned out to be a dirty .env file that was setting the CRAFT_WEB_URL too.

All systems back to normal AND the configuration has been simplified!

brandonkelly commented 6 months ago

Sweet! Glad you’re back up and running.

ishetnogferre commented 5 months ago

Thanks both for the feedback! Could you confirm if this is what you mean by setting it dynamically?

'@web' => $_SERVER['HTTP_HOST'] === 'craft.domain.com' ? App::env('BASE_CP_URL') : getenv('PRIMARY_SITE_URL'),

This is working locally without any issues (so far), I'm just not sure I took the most efficient approach.

I also attempted to solve via the other recommendation. So I tried NOT setting the @web alias and using the default cpTrigger. I thought this would resolve the domain issues but it didn't. Is there anything else I should be checking for?

@brandonkelly Following the above setup, we now have "broken" preview links in the CMS. We serve the CP from a custom domain "website-admin.be" instead of "website.be" and now the live preview URLs and globe URLs in the CMS have turned into "website-admin.be/web-page" instead of the normal "website.be/web-page" (which in turn give us different CORS errors.

We have the following setup from this article: https://craftcms.com/knowledge-base/access-control-panel-from-alternate-domain

Is this still the correct setup going forward or what do we need to change?

brandonkelly commented 5 months ago

@ishetnogferre What is your section’s preview target URL(s) set to?

ishetnogferre commented 5 months ago

@brandonkelly they're all set to {url}

brandonkelly commented 5 months ago

What’s the site URL?

ishetnogferre commented 5 months ago

We're setup like this:

'aliases' => [
    '@web' => App::env('PRIMARY_SITE_URL'),
 ]

with the site URL of the primary website being: "@web" and multisites like "@web/fr" or "@web/nl". Now that I'm typing that, is it correct we remove that alias and set the site URLs via that env var?

brandonkelly commented 5 months ago

@web should be set to the current requested hostname (and possibly a subpath, if index.php lives in a subdirectory of the webroot).

If your control panel has a different base URL than PRIMARY_SITE_URL, you should absolutely not be setting @web to that.

Instead, just stop setting @web altogether, and set your site’s base URL to $PRIMARY_SITE_URL instead.

casvangrunsven commented 2 months ago

@brandonkelly for me, unsetting @web or 'CRAFT_WEB_URL' did not work, I had to explicitly set 'CRAFT_WEB_URL' to '/var/www/html'

Actually, when unsettling the @web variable, my site does not resolve the subdomains anymore. So, for instance the baking subsite url (https://baking.webervacuum.group) is showing the default site (https://webervacuum.group). When setting this CRAFT_WEB_URL to the specific path, the subdomain website resolves correctly.

Can that do any harm?

brandonkelly commented 2 months ago

@brandonkelly for me, unsetting @web or 'CRAFT_WEB_URL' did not work, I had to explicitly set 'CRAFT_WEB_URL' to '/var/www/html'

If you’re going to set @web/CRAFT_WEB_URL, it needs to be set to a base URL, not a path. I have no idea how setting it to /var/www/html would work; it would theoretically cause all your URLs to be prefixed with /var/www/html. Are you sure you’re not looking at @webroot/CRAFT_WEB_ROOT (which is meant to be set to the path to the webroot)?

Actually, when unsettling the @web variable, my site does not resolve the subdomains anymore.

Go to UtilitiesPHP Info from a subdomain (e.g. https://baking.webervacuum.group/admin/utilities/php-info) and search for these values:

What are they set to?

casvangrunsven commented 2 months ago

Hi Brandon, thanks for your response!

@brandonkelly for me, unsetting @web or 'CRAFT_WEB_URL' did not work, I had to explicitly set 'CRAFT_WEB_URL' to '/var/www/html'

If you’re going to set @web/CRAFT_WEB_URL, it needs to be set to a base URL, not a path. I have no idea how setting it to /var/www/html would work; it would theoretically cause all your URLs to be prefixed with /var/www/html. Are you sure you’re not looking at @webroot/CRAFT_WEB_ROOT (which is meant to be set to the path to the webroot)?

Strangely enough, no. I really don't know why this is happening

Actually, when unsettling the @web variable, my site does not resolve the subdomains anymore.

Go to UtilitiesPHP Info from a subdomain (e.g. https://baking.webervacuum.group/admin/utilities/php-info) and search for these values:

  • HTTP_HOST
  • SERVER_NAME
  • X_FORWARDED_HOST
  • X_ORIGINAL_HOST

What are they set to?

When I unset the CRAFT_WEB_URL variable, those are the values. However, when I then navigate to baking.webervacuum.group it shows the contents of webervacuum.group on the front-end.

$_SERVER['HTTP_HOST'] baking.webervacuum.group $_SERVER['SERVER_NAME'] baking.webervacuum.group

X_FORWARDED_HOST and X_ORIGINAL_HOST are not available.

brandonkelly commented 2 months ago

When I unset the CRAFT_WEB_URL variable, those are the values. However, when I then navigate to baking.webervacuum.group it shows the contents of webervacuum.group on the front-end.

So that’s the issue. Both HTTP_HOST and SERVER_NAME should be getting set to the full host name (baking.webervacuum.group). If that’s not happening, it’s a server/infrastructure misconfiguration issue. Craft relies on those values being accurate so it knows which URL is being requested.

casvangrunsven commented 2 months ago

When I unset the CRAFT_WEB_URL variable, those are the values. However, when I then navigate to baking.webervacuum.group it shows the contents of webervacuum.group on the front-end.

So that’s the issue. Both HTTP_HOST and SERVER_NAME should be getting set to the full host name (baking.webervacuum.group). If that’s not happening, it’s a server/infrastructure misconfiguration issue. Craft relies on those values being accurate so it knows which URL is being requested.

Okay, so what should HTTP_HOST then be? For the baking site it should be baking.webervacuum.group, right?

Basically what happens is that when I set the CRAFT_WEB_URL value to https://webervacuum.group/ everything works, except Sprig and then I cannot navigate to the admin panel of the subdomain (e.g. baking.webervacuum.group)

However if I unset the CRAFT_WEB_URL value, I can navigate to the admin panel of the subdomain (e.g. baking.webervacuum.group) and HTTP_HOST is set to baking.webervacuum.group but the front-end is not working anymore.

Basically how my server is set up; it is nothing else than that the hostnames of each subdomain are pointing to the webroot of the main domain webervacuum.group (according to the documentation: https://craftcms.com/docs/5.x/system/sites.html)

brandonkelly commented 2 months ago

Okay, so what should HTTP_HOST then be? For the baking site it should be baking.webervacuum.group, right?

When you’re accessing baking.webervacuum.group, yes. And when you’re accessing webervacuum.group (no subdomain) it should be set to webervacuum.group.

HTTP_HOST should just be set to whatever the browser sets the Host header to, which is going to be the host name in the requested URL. So somewhere along the line, your server/infrastructure is “forgetting” the actual Host header value.

Basically how my server is set up; it is nothing else than that the hostnames of each subdomain are pointing to the webroot of the main domain webervacuum.group (according to the documentation: https://craftcms.com/docs/5.x/system/sites.html)

Yeah that’s normal. PHP should still be able to know what the requested URL was.

casvangrunsven commented 2 months ago

When you’re accessing baking.webervacuum.group, yes. And when you’re accessing webervacuum.group (no subdomain) it should be set to webervacuum.group.

HTTP_HOST should just be set to whatever the browser sets the Host header to, which is going to be the host name in the requested URL. So somewhere along the line, your server/infrastructure is “forgetting” the actual Host header value.

Sorry for bothering you, I just cannot seem to find the issue.

That is exactly what is happening at the moment, so then my server configuration is right I guess. Because when I navigate to Utilities → PHP Info from a subdomain (baking.webervacuum.group), both HTTP_HOST and SERVER_NAME are set to baking.webervacuum.group.

Below I pasted a screenshot of the footer, where I log two aliases from my general.php just to debug real quick '@host' => $_SERVER['HTTP_HOST'] & '@servername' => $_SERVER['SERVER_NAME'] and it seems that those HTTP_HOST and SERVER_NAME values are being set right by the server. However, in this setup, where CRAFT_WEB_URL and @web has been unset, the HTTP_HOST and SERVER_NAME on the front end are right but baking.webervacuum.group is showing the webervacuum.group website. The same holds for all other subsites. This is quite strange because, as you sead, the Host is being set correctly right?

Am I missing something?

brandonkelly commented 2 months ago

Sorry, I guess I misunderstood.

Can you create a template with the following code:

@web: {{ alias('@web') }}<br>
requested URL: {{ craft.app.request.url }}<br>
absolute URL: {{ craft.app.request.absoluteUrl }}<br><br>
current site: {{ currentSite.handle }}<br>
site base URLs:<br>
{{ craft.app.sites.allSites|map(s => "- #{s.handle}: #{s.getBaseUrl(false)} → #{s.baseUrl}<br>")|join('')|raw }}

And then load the template from each of the host names, and post the results for each of them?

casvangrunsven commented 2 months ago

Sorry, I guess I misunderstood.

Can you create a template with the following code:

@web: {{ alias('@web') }}<br>
requested URL: {{ craft.app.request.url }}<br>
absolute URL: {{ craft.app.request.absoluteUrl }}<br><br>
current site: {{ currentSite.handle }}<br>
site base URLs:<br>
{{ craft.app.sites.allSites|map(s => "- #{s.handle}: #{s.getBaseUrl(false)} → #{s.baseUrl}<br>")|join('')|raw }}

And then load the template from each of the host names, and post the results for each of them?

No worries. Maybe there is just something wrong with my English and am I not writing clear enough :) You can find any url at webervacuum.group/info, baking.webervacuum.group/info, drying.webervacuum.group/info, cooling.webervacuum.group/info (password webervacuum)

Just did: when unsetting @web (so when the website breaks, and each subdomain shows the content of the main domain), the following holds for baking.webervacuum.group. So in some way, Craft CMS (or the server) does not set the @web correctly

@web: https://baking.webervacuum.group requested URL: /info absolute URL: https://baking.webervacuum.group/info

current site: default site base URLs:

When setting @web, it gives the following for baking.webervacuum.group: @web: https://webervacuum.group requested URL: /info absolute URL: https://baking.webervacuum.group/info

current site: baking site base URLs:

brandonkelly commented 2 months ago

The problem is that your default site has its base URL set to @web.

Consider that when @web is set correctly (where it exactly matches the incoming request URL), that means that no matter what URL is requested, your default site’s base URL is going to be an exact match.

You should not ever be referencing @web in your site’s base URL, especially if you have multiple sites. Instead you must hard-code it, or define another alias such as @mainSiteUrl, set to https://webervacuum.group/, and adjust the base URLs for each of the four sites that currently reference @web, so they referenc @mainSiteUrl instead.