What is CORS?

Cross-Origin Resource Sharing (CORS) is a security mechanism that is based on HTTP headers. The idea is to restrict resources to be loaded or interacted with from the same domain or from all.

When you have an API that maybe called via ajax request from one of your domains you can either use the following which is not very secure because it can be called by ANY site. This is very convenient because it will always allow requests but it's unsecure.

header('Access-Control-Allow-Origin: *');

Is there a better way? Yes, of course.

How to send CORS response headers in PHP more securely?

As always it comes down to validation and sanitization of the received data.

Each valid ajax request must send the Origin request header along with the rest of the request. PHP (or Apache) makes that available as $_SERVER['HTTP_ORIGIN'] variable.

The code below checks several fields until it finds one. The default CORS is localhost so if the request is not correct it will just send a not very useful CORS header.

Then the code sanitizes the value and finally checks it if it ends in known sites i.e. your domains.

You need to put your sites in the regular expression below (see variable $allowed_domains_regex) and separate them with a comma. The Origin header contains the site without any path or variables attached to it. That's why we're checking if it ends in a specific domain. The very end is optional as it should match lots of domains.

Then just use this function in your code.

Usage

Orbisius_CORS_Util::sendCors();

Use the following code in a php file and load it.

<?php

/**
 * To request a customization or hire us contact us at - https://orbisius.com/contact
 * @see Blog post https://orbisius.com/7146
 * @author Svetoslav (Slavi) Marinov
 * @copyright (c) 2022-3000 All Rights Reserved.
 * @license MIT
 */
class Orbisius_CORS_Util {
    /**
     * Orbisius_CORS_Util::sendCors();
     * @return void
     */
    public static function sendCors() {
        if (headers_sent()) {
            return;
        }

        $inp_origin = '';
        $cors_origin = 'localhost'; // * is not secure
        $allowed_domains_regex = '#(YOUR_SITES|SEPARATED_BY_PIPE_BECAUSE_OF_REGEX)(\.[a-z]{0,2})?$#si';

        // Allow ajax requests from my domains only
        if (!empty($_SERVER['HTTP_ORIGIN'])) {
            $inp_origin = $_SERVER['HTTP_ORIGIN'];
        } elseif (!empty($_SERVER['HTTP_HOST'])) {
            $inp_origin = $_SERVER['HTTP_HOST'];
        } elseif (!empty($_SERVER['SERVER_NAME'])) {
            $inp_origin = $_SERVER['HTTP_HOST'];
        }

        $inp_origin = strip_tags($inp_origin);
        $inp_origin = trim($inp_origin);

        if (!empty($inp_origin) && preg_match($allowed_domains_regex, $inp_origin)) {
            $cors_origin = $inp_origin;
        }

        header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
        header('Access-Control-Allow-Credentials: true');
        header("Access-Control-Allow-Headers: Origin,Content-Type,Authorization,Cache-Control,X-Requested-With,X-Auth-Token,X-XSRF-TOKEN");
        header('Access-Control-Allow-Origin: ' . $cors_origin); // safe? smart? to allow access from anywhere?
    }
}

Using WordPress

If you're using WordPress you can use send_origin_headers() function to send the proper CORS headers.

Do you see how this can be improved in any way?

image credit: Scott Webb (unsplash) id: yekGLpc3vro


Disclaimer: The content in this post is for educational purposes only. Always remember to take a backup before doing any of the suggested steps just to be on the safe side.
Referral Note: When you purchase through a referral link (if any) on this page, we may earn a commission.
If you're feeling thankful, you can buy me a coffee or a beer