Due to the integration of CSP to my site, I had to turn off Turbolinks. It just wasn't working together; until now!

My last week was all about Content Security Policy (CSP). It was an emotional rollercoaster. I loved the concept of CSP and was happy that I managed to integrate it into my site. But then I noticed that caching and Turbolinks weren't working anymore because of the CSP nonces. I had to turn them off 😔 Then yesterday, I found a way to use CSP nonces with the Laravel Response Caching package. I was super excited about it.



Still, Turbolinks weren't working.

The Problem

Turbolinks help you to make your site faster. Instead of doing a full page-load, when the user clicks a link, Turbolinks makes an AJAX request to grab the new page. It then changes the content of the current site's body, as well as the current URL. It feels like a single page application.



But this also means the response header stays the same. It doesn't get updated. This leads to a problem when you use CSP nonces. They are generated for every request and help you to verify scripts or styles on your website. I used them to allow my inline Google Analytics and Facebook SDK script. They got refused by the browser, and this was the reason why I turned Turbolinks off.

The Solution

After a little discussion on GitHub, I got a golden hint: CSP hash-algorithm. Besides validating your inline scripts or styles with a nonce, it is possible to use a hash-algorithm. It works like this:

You hash your inline script

You add this hash to your CSP header

The browser can now verify it without a nonce

Awesome! That was exactly what I was looking for. I heard of it before, but I didn't realize it could help me with my problem. After adding two new resources to my CSP headers, my site was working without the nonces and the custom middleware I built. This solution was even better because I was able to activate Turbolinks again. 🎉



These are my updated CSP methods:

private function addGoogleAnalyticsPolicies () { $this ->addDirective(Directive::SCRIPT, [ '*.googletagmanager.com' , '*.google-analytics.com' , ]) ->addDirective(Directive::IMG, '*.google-analytics.com' ) ->addDirective(Directive::SCRIPT, '\'sha256-2eu3x9C6JPt7NvPk4iAcvrQ2g+UHBEyUsilOqkWukiU=\'' ); } private function addFacebookChatbotPolicies () { $this ->addDirective(Directive::SCRIPT, '*.facebook.net' ) ->addDirective(Directive::IMG, '*.facebook.com' ) ->addDirective(Directive::FRAME, '*.facebook.com' ) ->addDirective(Directive::STYLE, 'unsafe-inline' ) ->addDirective(Directive::SCRIPT, '\'sha256-P70IONn7LzR0v1pnyUiwOX+9oJzqbc7ZGp+eujcwZsE=\'' ); }

Read more about how the hashing works on the MDN web docs.

Note: In most browsers CSP error messages, you can also see the hash of the failing script or style. You can just grab it from there and don't need to hash it yourself.

Browser Compatibility

The Hash-Algorithm feature belongs to CSP 2.0 and therefore don't work in browsers like the Internet Explorer. Take a look at Can I use for more details.

Conclusion

This week I had to deal with CSP much more than I wanted to, but it paid off. The integration and problems that followed helped me to get a better understanding of CSP and real-world use-cases. I am glad I was able to show solutions for making it work with response caching and Turbolinks.