How to set CORS headers for built assets

And do it efficiently

Joel Clermont
Joel Clermont
2024-03-08

If you haven't read yesterday's tip on Vite and remote sites loading modules via script tag, start there first for full context.

So what is the solution? It doesn't actually involve Laravel.

Normally, we could configure our CORS headers in the config/cors.php file, but that only works if Laravel is serving the request. In our case, the built assets don't go through Laravel. Our web server serves them directly (and more efficiently).

We could force Laravel to serve up our built assets just so we could have it add the CORS headers for us, but that feels like a hacky solution and has performance implications.

Instead, we can use our web server to specify the CORS headers. And we can be selective so that we don't add unnecessary headers to every request.

Here's how to do it with Nginx:

# inside our nginx.conf, before our index.php and php-fpm directives

location ~ ^/build/js/embed/(.*)$ {
    if ($request_method = 'OPTIONS') {
        # we're using a wildcard origin here, but you could specify exact domains if you need
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
     }
    if ($request_method = 'POST') {
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
    }
    if ($request_method = 'GET') {
        add_header 'Access-Control-Allow-Origin' '*' always;
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
    }
}

I know what you're thinking: couldn't you merge some of those if blocks? Nope, nginx handles if differently than PHP and this is actually the cleanest way to do it with those constraints.

Similar options exist for other web servers. enable-cors.org is a great reference (and where I got most of the above config from).

Or maybe you're publishing your assets to a CDN that sets these headers for you already. If so, you're all set.

Here to help,

Joel

P.S. After reading this tip, if your brain is in a security frame of mind, check out our free book on Laravel security.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼

Level up your Laravel skills!

Each 2-minute email has real-world advice you can use.