Skip to content

Instantly share code, notes, and snippets.

@agungsugiarto
Last active September 18, 2025 05:02
Show Gist options
  • Select an option

  • Save agungsugiarto/28ff3eaf8e64b5a87de85e7f5e2e6b58 to your computer and use it in GitHub Desktop.

Select an option

Save agungsugiarto/28ff3eaf8e64b5a87de85e7f5e2e6b58 to your computer and use it in GitHub Desktop.
Dealing with CORS in CodeIgniter 3

1. Add a config file named cors.php in the application/config directory.

<?php

defined('BASEPATH') or exit('No direct script access allowed');

$config = [
    /*
     |--------------------------------------------------------------------------
     | Cross-Origin Resource Sharing (CORS) Configuration
     |--------------------------------------------------------------------------
     |
     | Here you may configure your settings for cross-origin resource sharing
     | or "CORS". This determines what cross-origin operations may execute
     | in web browsers. You are free to adjust these settings as needed.
     |
     | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
     |
     */
    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => false,
];

Options

Option Description Default value
allowed_methods Matches the request method. ['*']
allowed_origins Matches the request origin. ['*']
allowed_origins_patterns Matches the request origin with preg_match. []
allowed_headers Sets the Access-Control-Allow-Headers response header. ['*']
exposed_headers Sets the Access-Control-Expose-Headers response header. []
max_age Sets the Access-Control-Max-Age response header. 0
supports_credentials Sets the Access-Control-Allow-Credentials header. false

The allowed_methods and allowed_headers options are case-insensitive.

You don't need to provide both allowed_origins and allowed_origins_patterns. If one of the strings passed matches, it is considered a valid origin. A wildcard in allowed_origins will be converted to a pattern.

If ['*'] is provided to allowed_methods, allowed_origins or allowed_headers all methods / origins / headers are allowed.

Note: Allowing a single static origin will improve cacheability.

2. Next, add the Cors class to the application/libraries folder and name the file Cors.php.

<?php

defined('BASEPATH') or exit('No direct script access allowed');

class Cors
{
    /** @var CI_Controller */
    protected $ci;

    /** @var string[]  */
    private array $allowedOrigins = [];
    /** @var string[] */
    private array $allowedOriginsPatterns = [];
    /** @var string[] */
    private array $allowedMethods = [];
    /** @var string[] */
    private array $allowedHeaders = [];
    /** @var string[] */
    private array $exposedHeaders = [];
    private bool $supportsCredentials = false;
    private ?int $maxAge = 0;

    private bool $allowAllOrigins = false;
    private bool $allowAllMethods = false;
    private bool $allowAllHeaders = false;

    public function __construct()
    {
        $this->ci = &get_instance();
        $this->ci->load->config('cors', true);

        $this->setOptions($this->ci->config->item('cors'));
    }

    public function handle()
    {
        // For Preflight, return the Preflight response
        if ($this->isPreflightRequest($this->ci->input)) {
            $this->handlePreflightRequest($this->ci->input);
            $this->varyHeader($this->ci->output, 'Access-Control-Request-Method');
        }

        if ($this->ci->input->method(true) === 'OPTIONS') {
            $this->varyHeader($this->ci->output, 'Access-Control-Request-Method');
        }

        if (!$this->ci->output->get_header('Access-Control-Allow-Origin')) {
            // Add the CORS headers to the Response
            $this->addActualRequestHeaders($this->ci->output, $this->ci->input);
        }
    }

    public function setOptions(array $options): void
    {
        $this->allowedOrigins = $options['allowed_origins'] ?? $this->allowedOrigins;
        $this->allowedOriginsPatterns = $options['allowed_origins_patterns'] ?? $this->allowedOriginsPatterns;
        $this->allowedMethods = $options['allowed_methods'] ?? $this->allowedMethods;
        $this->allowedHeaders = $options['allowed_headers'] ?? $this->allowedHeaders;
        $this->supportsCredentials = $options['supports_credentials'] ?? $this->supportsCredentials;

        $maxAge = $this->maxAge;
        if (array_key_exists('maxAge', $options)) {
            $maxAge = $options['maxAge'];
        } elseif (array_key_exists('max_age', $options)) {
            $maxAge = $options['max_age'];
        }
        $this->maxAge = $maxAge === null ? null : (int)$maxAge;

        $exposedHeaders =  $options['exposed_headers'] ?? $this->exposedHeaders;
        $this->exposedHeaders = $exposedHeaders === false ? [] : $exposedHeaders;

        $this->normalizeOptions();
    }

    private function normalizeOptions(): void
    {
        // Normalize case
        $this->allowedHeaders = array_map('strtolower', $this->allowedHeaders);
        $this->allowedMethods = array_map('strtoupper', $this->allowedMethods);

        // Normalize ['*'] to true
        $this->allowAllOrigins = in_array('*', $this->allowedOrigins);
        $this->allowAllHeaders = in_array('*', $this->allowedHeaders);
        $this->allowAllMethods = in_array('*', $this->allowedMethods);

        // Transform wildcard pattern
        if (!$this->allowAllOrigins) {
            foreach ($this->allowedOrigins as $origin) {
                if (strpos($origin, '*') !== false) {
                    $this->allowedOriginsPatterns[] = $this->convertWildcardToPattern($origin);
                }
            }
        }
    }

    /**
     * Create a pattern for a wildcard, based on Str::is() from Laravel
     *
     * @see https://github.com/laravel/framework/blob/5.5/src/Illuminate/Support/Str.php
     * @param string $pattern
     * @return string
     */
    private function convertWildcardToPattern($pattern)
    {
        $pattern = preg_quote($pattern, '#');

        // Asterisks are translated into zero-or-more regular expression wildcards
        // to make it convenient to check if the strings starts with the given
        // pattern such as "*.example.com", making any string check convenient.
        $pattern = str_replace('\*', '.*', $pattern);

        return '#^' . $pattern . '\z#u';
    }

    public function isCorsRequest(CI_Input $request): bool
    {
        return $request->get_request_header('Origin') !== null;
    }

    public function isPreflightRequest(CI_Input $request): bool
    {
        return $request->method(true) === 'OPTIONS' && $request->get_request_header('Access-Control-Request-Method');
    }

    public function handlePreflightRequest(CI_Input $request): CI_Output
    {
        /** @var CI_Output $response */
        $response = $this->ci->output;

        $response->set_status_header(204);

        return $this->addPreflightRequestHeaders($response, $request);
    }

    public function addPreflightRequestHeaders(CI_Output $response, CI_Input $request): CI_Output
    {
        $this->configureAllowedOrigin($response, $request);

        if ($response->get_header('Access-Control-Allow-Origin')) {
            $this->configureAllowCredentials($response);
            $this->configureAllowedMethods($response, $request);
            $this->configureAllowedHeaders($response, $request);
            $this->configureMaxAge($response);
        }

        return $response;
    }

    public function isOriginAllowed(CI_Input $request): bool
    {
        if ($this->allowAllOrigins === true) {
            return true;
        }

        $origin = $request->get_request_header('Origin');

        if (in_array($origin, $this->allowedOrigins)) {
            return true;
        }

        foreach ($this->allowedOriginsPatterns as $pattern) {
            if (preg_match($pattern, $origin)) {
                return true;
            }
        }

        return false;
    }

    public function addActualRequestHeaders(CI_Output $response, CI_Input $request): CI_Output
    {
        $this->configureAllowedOrigin($response, $request);

        if ($response->get_header('Access-Control-Allow-Origin')) {
            $this->configureAllowCredentials($response);
            $this->configureExposedHeaders($response);
        }

        return $response;
    }

    private function configureAllowedOrigin(CI_Output $response, CI_Input $request): void
    {
        if ($this->allowAllOrigins === true && !$this->supportsCredentials) {
            // Safe+cacheable, allow everything
            $response->set_header('Access-Control-Allow-Origin: *');
        } elseif ($this->isSingleOriginAllowed()) {
            // Single origins can be safely set
            $response->set_header(sprintf('Access-Control-Allow-Origin: %s', array_values($this->allowedOrigins)[0]));
        } else {
            // For dynamic headers, set the requested Origin header when set and allowed
            if ($this->isCorsRequest($request) && $this->isOriginAllowed($request)) {
                $response->set_header("Access-Control-Allow-Origin: {$request->get_request_header('origin')}");
            }

            $this->varyHeader($response, 'Origin');
        }
    }

    private function isSingleOriginAllowed(): bool
    {
        if ($this->allowAllOrigins === true || count($this->allowedOriginsPatterns) > 0) {
            return false;
        }

        return count($this->allowedOrigins) === 1;
    }

    private function configureAllowedMethods(CI_Output $response, CI_Input $request): void
    {
        if ($this->allowAllMethods === true) {
            $allowMethods = strtoupper($request->get_request_header('Access-Control-Request-Method'));
            $this->varyHeader($response, 'Access-Control-Request-Method');
        } else {
            $allowMethods = implode(', ', $this->allowedMethods);
        }

        $response->set_header("Access-Control-Allow-Methods: {$allowMethods}");
    }

    private function configureAllowedHeaders(CI_Output $response, CI_Input $request): void
    {
        if ($this->allowAllHeaders === true) {
            $allowHeaders = $request->get_request_header('Access-Control-Request-Headers');
            $this->varyHeader($response, 'Access-Control-Request-Headers');
        } else {
            $allowHeaders = implode(', ', $this->allowedHeaders);
        }
        $response->set_header("Access-Control-Allow-Headers: {$allowHeaders}");
    }

    private function configureAllowCredentials(CI_Output $response): void
    {
        if ($this->supportsCredentials) {
            $response->set_header('Access-Control-Allow-Credentials: true');
        }
    }

    private function configureExposedHeaders(CI_Output $response): void
    {
        if ($this->exposedHeaders) {
            $response->set_header(sprintf('Access-Control-Expose-Headers: %s', implode(', ', $this->exposedHeaders)));
        }
    }

    private function configureMaxAge(CI_Output $response): void
    {
        if ($this->maxAge !== null) {
            $response->set_header("Access-Control-Max-Age: {$this->maxAge}");
        }
    }

    public function varyHeader(CI_Output $response, string $header): CI_Output
    {
        if (!$response->get_header('Vary')) {
            $response->set_header("Vary: {$header}");
        } elseif (!in_array($header, explode(', ', $response->get_header('Vary')))) {
            $response->set_header("Vary: {$response->get_header('Vary')}, {$header}");
        }

        return $response;
    }
}

3. In the constructor of your controller, add this line of code.

<?php

class API_Controller extends CI_Controller
{
    public function __construct()
    {
        parent::__construct();

        $this->load->library('cors');
        $this->cors->handle();
    }
}

Reference

  1. Cross-Origin Resource Sharing (CORS)
  2. CORS for PHP (using the Symfony HttpFoundation)
  3. CORS Filter for CodeIgniter 4
@agungsugiarto
Copy link
Author

agungsugiarto commented Aug 17, 2023

cara penggunaan yang lebih spesifik gak ada pak? seperti hanya domain tertentu ya bisa akses API tersebut

@mdestafadilah bisa di setting melalui config corsnya pada bagian 'allowed_origins' => ['*'], contoh 'allowed_origins' => ['https://google.com', '*.google.com'],

@joko2020
Copy link

joko2020 commented Dec 2, 2023

ini nyoba cross domain , manggil login page... tetep nggak bisa ya kalo dimasukin ke Iframe

@sogrbilja
Copy link

The syntax private array $allowedOrigins = []; won't work in PHP 7.3.33. Typed properties were introduced in PHP 7.4. You should remove these.

@mdestafadilah
Copy link

The syntax private array $allowedOrigins = []; won't work in PHP 7.3.33. Typed properties were introduced in PHP 7.4. You should remove these.

thanks.

@EgiGrivandani
Copy link

untuk implementasi di hooks (pre_system) itu gimana mas ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment