Skip to content

CORS in Leaf MVC

MVC HTTP access

Configure browser access from your app environment.

Leaf MVC wires CORS into the app for you, then lets production apps tighten allowed origins, methods, and headers through environment config or a published config file.

CORS_ALLOWED_ORIGINS='https://app.example.com'
CORS_ALLOWED_METHODS='GET,POST'
leaf config:publish cors

CORS is the browser security layer that decides which origins can read responses from your app. Since CORS is a common pain point for web developers, Leaf provides a first-party integration that takes care of the repetitive setup for you.

Setting Up

You can install the CORS module through the Leaf CLI or with composer.

bash
leaf install cors
bash
composer require leafs/cors

After installing the CORS module, Leaf MVC will automatically set up CORS to handle all incoming requests without any limits. While this is great for development, it's not recommended for production. You can customize the CORS settings using the CORS config.

Configuring CORS

Most of the configuration options can be configured using environment variables. Here are the available options:

.env
txt
CORS_ALLOWED_ORIGINS='https://app.example.com'
CORS_ALLOWED_METHODS='GET,HEAD,PUT,PATCH,POST,DELETE'
CORS_ALLOWED_HEADERS='*'

Origins are matched exactly, so each configured origin must be a full origin including the scheme, like https://app.example.com. Partial values like example.com will not match. To allow a whole family of origins, such as every subdomain of a site, use a regular expression written as a string:

.env
txt
CORS_ALLOWED_ORIGINS='/^https:\/\/(.*\.)?example\.com$/'

While this is easier and allows you to easily configure different environments, it can sometimes be limiting, for example when you want to allow an array that mixes exact origins and regex strings. For this reason, you can publish your CORS configuration using the command below:

bash
leaf config:publish cors

This will publish the CORS config file to config/cors.php, where you can customize the settings to your liking.

php
<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Configure allowed origins
    |--------------------------------------------------------------------------
    |
    | Configures the Access-Control-Allow-Origin CORS header. Possible values:
    |
    | * String - set origin to a specific origin, including the scheme.
    |   For example if you set it to "https://example.com" only requests
    |   from "https://example.com" will be allowed. Origins are matched
    |   exactly; partial values like "example.com" will not match.
    |
    | * Regex string - set origin to a regular expression written as a
    |   string, which will be tested against the request origin. If it
    |   matches, the request origin will be reflected. For example
    |   '/^https:\/\/(.*\.)?example\.com$/' will allow
    |   "https://example.com" and any of its subdomains.
    |
    | * Array - set origin to an array of valid origins. Each origin can
    |   be an exact origin or a regex string. For example
    |   ['https://example1.com', '/^https:\/\/(.*\.)?example2\.com$/']
    |   will accept requests from "https://example1.com" or from
    |   "example2.com" and its subdomains.
    |
    */
    'origin' => _env('CORS_ALLOWED_ORIGINS', '*'),

    /*
    |--------------------------------------------------------------------------
    | Configure allowed HTTP methods
    |--------------------------------------------------------------------------
    |
    | Configures the Access-Control-Allow-Methods CORS header.
    | Expects a comma-delimited string (ex: 'GET,PUT,POST') or
    | an array (ex: ['GET', 'PUT', 'POST'])
    |
    */
    'methods' => _env('CORS_ALLOWED_METHODS', 'GET,HEAD,PUT,PATCH,POST,DELETE'),

    /*
    |--------------------------------------------------------------------------
    | Configure allowed HTTP headers
    |--------------------------------------------------------------------------
    |
    | Configures the Access-Control-Allow-Headers CORS header. Expects a
    | comma-delimited string (ex: 'Content-Type,Authorization') or
    | an array (ex: ['Content-Type', 'Authorization']). If not specified,
    | defaults to reflecting the headers specified in the request's
    | Access-Control-Request-Headers header.
    |
    */
    'allowedHeaders' => _env('CORS_ALLOWED_HEADERS', '*'),

    /*
    |--------------------------------------------------------------------------
    | Configure expose headers
    |--------------------------------------------------------------------------
    |
    | Configures the Access-Control-Expose-Headers CORS header. Expects
    | a comma-delimited string (ex: 'Content-Range,X-Content-Range')
    | or an array (ex: ['Content-Range', 'X-Content-Range']).
    | If not specified, no custom headers are exposed.
    |
    */
    'exposedHeaders' => _env('CORS_EXPOSED_HEADERS', ''),

    /*
    |--------------------------------------------------------------------------
    | Configure credentials
    |--------------------------------------------------------------------------
    |
    | Configures the Access-Control-Allow-Credentials CORS header.
    | Set to true to pass the header, otherwise it is omitted.
    |
    */
    'credentials' => false,

    /*
    |--------------------------------------------------------------------------
    | Configure max age
    |--------------------------------------------------------------------------
    |
    | Configures the Access-Control-Max-Age CORS header. Set to
    | an integer to pass the header, otherwise it is omitted.
    |
    */
    'maxAge' => null,

    /*
    |--------------------------------------------------------------------------
    | Configure preflight continue
    |--------------------------------------------------------------------------
    |
    | Pass the CORS preflight response to the next handler.
    |
    */
    'preflightContinue' => false,

    /*
    |--------------------------------------------------------------------------
    | Log open
    |--------------------------------------------------------------------------
    |
    | Provides a status code to use for successful OPTIONS requests,
    | since some legacy browsers (IE11, various SmartTVs) choke on 204.
    |
    */
    'optionsSuccessStatus' => 204,
];

Configuration Options

The cors() method takes in an array of options. Here are the available options:

  • origin: Configures the Access-Control-Allow-Origin CORS header. Possible values:

    • String - set origin to a specific origin, including the scheme. For example if you set it to "https://example.com" only requests from "https://example.com" will be allowed. Origins are matched exactly; partial values like "example.com" will not match.
    • Regex string - set origin to a regular expression written as a string, which will be tested against the request origin. If it matches, the request origin will be reflected. For example '/^https:\/\/(.*\.)?example\.com$/' will allow "https://example.com" and any of its subdomains.
    • Array - set origin to an array of valid origins. Each origin can be an exact origin or a regex string. For example ['https://example1.com', '/^https:\/\/(.*\.)?example2\.com$/'] will accept requests from "https://example1.com" or from "example2.com" and its subdomains.
  • methods: Configures the Access-Control-Allow-Methods CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: ['GET', 'PUT', 'POST']).

  • allowedHeaders: Configures the Access-Control-Allow-Headers CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: ['Content-Type', 'Authorization']). If not specified, defaults to reflecting the headers specified in the request's Access-Control-Request-Headers header.

  • exposedHeaders: Configures the Access-Control-Expose-Headers CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: ['Content-Range', 'X-Content-Range']). If not specified, no custom headers are exposed.

  • credentials: Configures the Access-Control-Allow-Credentials CORS header. Set to true to pass the header, otherwise it is omitted. When enabling credentials, use explicit origins rather than '*', since browsers reject credentialed responses that allow every origin.

  • maxAge: Configures the Access-Control-Max-Age CORS header. Set to an integer to pass the header, otherwise it is omitted.

  • preflightContinue: Pass the CORS preflight response to the next handler.

  • optionsSuccessStatus: The status code returned for successful preflight OPTIONS requests, since some legacy browsers (IE11, various SmartTVs) choke on 204. Set it to 200 if you need to support those clients.

The default configuration is the equivalent of:

json
{
  "origin": "*",
  "methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
  "allowedHeaders": "*",
  "exposedHeaders": "",
  "credentials": false,
  "maxAge": null,
  "preflightContinue": false,
  "optionsSuccessStatus": 204,
}

Now that CORS is handled, here are a few other parts of Leaf and Leaf MVC worth a look:

  • Authentication

    Learn more about routing in Leaf MVC, dynamic routes, middleware and more.

  • Roles & Permissions

    Learn how to process incoming requests, handle form submissions, and more.

  • Session Data

    Save user data, flash messages, and more using Leaf's session module.

  • Frontend

    Learn about SSR, SPA, and how to use Leaf with your favorite frontend framework.