Content Security Policy, Hash-Algorithm and Turbolinks
#securityDue 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.
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.