Categories
Software Engineering

Apache + Ubuntu + CORS + Multiple Subdomains + PHP Session Cookies + XHR Credentials

Overview:

I recently had to do a little hunting around for an issue related to everything you see in the title.

It was frustrating because I wanted to allow * domains to be able to send requests and the Stack Overflow comments are mostly about IIS servers or the ones with Apache directives as examples don’t seem to work or don’t handle full wildcard.

I should mention that allowing full wildcard Access-Control-Allow-Origin with XHR credentials enabled can be a security risk.

Here’s how to get a CORS request working with Ubuntu Apache and PHP across multiple subdomains and have the PHP session ID cookie send correctly on every request and how to handle some of the errors you may encounter.

Situation:

I was making an AJAX request from one subdomain (https://subdomain1.example.com) to a different subdomain on the same root domain (https://subdomain2.example.com).

I had already called a PHP session_start(); for the user on a page on the subdomain I was making the request to. This means the user has an active PHP $_SESSION on https://subdomain2.example.com.

I had set the PHP $_SESSION to use cookies at the root domain.

I had Header set Access-Control-Allow-Origin "*" in my .htaccess file.

I was using the following setup for calling session_start() on top of the PHP defaults. Be careful setting cookies at your root domain, they will be sent along with *all* futures requests to *all* subdomains:

ini_set('session.use_cookies', 'On');
ini_set('session.use_trans_sid', 'Off');
ini_set('session.cookie_domain', '.example.com');
session_set_cookie_params(0, '/', '.example.com');
session_name('example_session');
session_start();


Expected:

When making an AJAX CORS request the cookies, (and therefore the PHP $_SESSION session_id() being passed as a cookie) the should be sent along correctly with the HTTP request.

Actual:

All cookies, including the PHP $_SESSION session_id(), are not being passed along as part of the request.


Errors:

You may see this situation manifest as number of errors in your Javascript Developer Console.


Error:  The 'Access-Control-Allow-Origin' header contains multiple values , but only one is allowed.

Reason: You are trying to set more than 1 value for Access-Control-Allow-Origin

Solution: Use a series of Apache statements in your .htaccess file that dynamically assigns the Access-Control-Allow-Origin to whatever the incoming request is. (If you would like to use wildcard domains please see the solution further down the page for wildcards.)

Code:

<IfModule mod_headers.c>
SetEnvIf Origin ^(https?://.+\.example\.com(?::\d{1,5})?)$ CORS_ALLOW_ORIGIN=$1

Header append Access-Control-Allow-Origin %{CORS_ALLOW_ORIGIN}e env=CORS_ALLOW_ORIGIN

Header merge Vary "Origin"
</IfModule mod_headers.c>


Error:  The 'Access-Control-Allow-Origin' header has a value that is not equal to the supplied origin.

Reason: You are already setting a Access-Control-Allow-Origin or you are setting it incorrectly. You may be making multiple Header set Access-Control-Allow-Origin statements (because there can only be one domain set the last one overwrites any previous set commands.)

Solution: Use a series of Apache statements in your .htaccess file that dynamically assigns the Access-Control-Allow-Origin to whatever the incoming request is. (If you would like to use wildcard domains please see the solution further down the page for wildcards.)

Code:

<IfModule mod_headers.c>
SetEnvIf Origin ^(https?://.+\.example\.com(?::\d{1,5})?)$ CORS_ALLOW_ORIGIN=$1

Header append Access-Control-Allow-Origin %{CORS_ALLOW_ORIGIN}e env=CORS_ALLOW_ORIGIN

Header merge Vary "Origin"
</IfModule mod_headers.c>


Error:  The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Reason: You are trying to set "*" for your Access-Control-Allow-Origin but when you are using credentials you can only set it to 1 specific subdomain.

Solution: If you want to use a full wildcard style entry to accept requests from any origin and let them send in cookies then use a series of Apache statements in your .htaccess file that dynamically assigns the Access-Control-Allow-Origin to whatever the incoming request origin is. The first SetEnv handles the root domain wildcard and the second SetEnv handles the wildcard subdomains.

Code:

<IfModule mod_headers.c>
SetEnvIf Origin ^(https?://.+\..+(?::\d{1,5})?)$ CORS_ALLOW_ORIGIN=$1
SetEnvIf Origin ^(https?://.+\..+\..+(?::\d{1,5})?)$ CORS_ALLOW_ORIGIN=$1

Header append Access-Control-Allow-Origin %{CORS_ALLOW_ORIGIN}e env=CORS_ALLOW_ORIGIN

Header merge Vary "Origin"
</IfModule mod_headers.c>


Error:  The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

Reason: You are trying to send along the cookies as part of the AJAX request but your server is not current accepting the incoming cookies from the origin domain using Access-Control-Allow-Credentials

Solution: Use an Apache statement in your .htaccess file to always accept incoming credentials (aka cookies)

Code:

<IfModule mod_headers.c>
Header always set Access-Control-Allow-Credentials true
</IfModule mod_headers.c>


Error:  The PHP $_SESSION cookie is not being passed correctly to the server. It is not available in $_COOKIE and new calls to session_start() create a session with a new ID.

Reason: You are not correctly sending along the cookies with the AJAX request.

Solution: Use jquery AJAX and set the xhrFields to have withCredentials set to true

Code:

$.ajax({
xhrFields: {
withCredentials: true
}
});