Understanding CSPs through Hydrogen
Content Security Policy (CSP) is a crucial tool in web development that can make or break your site’s security. If you’ve spent time debugging font loading issues or mysterious errors while working with 3rd party imports, you’ve likely bumped into CSP issues.
In this post, I will break down what a CSP is, how it works in Hydrogen (can translate to any other framework), and how you can adjust it to fit your specific needs using a real-world example from my own projects.
Note: Hydrogen is Shopify's React framework, built on top of Remix
What is Content Security Policy (CSP)?
Content Security Policy (CSP) is a feature that protects your site cyber attacks like Cross-Site Scripting (XSS) and data injection. Essentially, CSP allows you to define sources from which your site can load resources; such as scripts, styles, and fonts.
By setting up a CSP, you tell the browser exactly which sources are trusted, and the browser will block anything not explicitly allowed. This is extremely useful in securing yourself against unauthorized scripts/styles from executing on your site.
How CSP Works
CSP works by using HTTP headers to specify the policy. The browser reads this policy and applies it to all resources loaded by your site. If a resource comes from a source not specified in your CSP, it will be blocked, and you might see errors in the console like:
Refused to load the font 'https://fonts.gstatic.com/s/opensans/v40/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6FxZCJgvAQ.woff2' because it violates the following Content Security Policy directive: "font-src 'self' https://fonts.googleapis.com https://maxcdn.bootstrapcdn.com https://use.typekit.net".
This is the browser telling you that your current CSP is too restrictive for the resources you’re trying to load.
Using CSP in Shopify Hydrogen
In Hydrogen, you’ll typically control the CSP in entry.server.jsx
or server.jsx
.
Hydrogen is a React-based framework for building headless Shopify storefronts, and managing security in such environments is critical.
Here's a glimpse into how CSP is typically set up in Hydrogen:
import {RemixServer} from '@remix-run/react';
import isbot from 'isbot';
import {renderToReadableStream} from 'react-dom/server';
import {createContentSecurityPolicy} from '@shopify/hydrogen';
import { CartProvider } from '@shopify/hydrogen-react';
export default async function handleRequest(
request,
responseStatusCode,
responseHeaders,
remixContext,
) {
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
styleSrc: [
"'self'",
"'unsafe-inline'",
'https://cdn.shopify.com',
'https://cdnjs.cloudflare.com',
'https://fonts.googleapis.com',
'https://maxcdn.bootstrapcdn.com',
'https://use.typekit.net',
'https://p.typekit.net', // Added for Adobe Fonts
],
fontSrc: [
"'self'",
'https://fonts.googleapis.com',
'https://maxcdn.bootstrapcdn.com',
'https://use.typekit.net',
'https://fonts.gstatic.com', // Added for Google Fonts
],
});
const body = await renderToReadableStream(
<NonceProvider>
<CartProvider>
<RemixServer context={remixContext} url={request.url} />
</CartProvider>
</NonceProvider>,
{
nonce,
signal: request.signal,
onError(error) {
console.error(error);
responseStatusCode = 500;
},
},
);
if (isbot(request.headers.get('user-agent'))) {
await body.allReady;
}
responseHeaders.set('Content-Type', 'text/html');
responseHeaders.set('Content-Security-Policy', header);
return new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
});
}
In this example, CSP is generated using the createContentSecurityPolicy
function. The styleSrc
and fontSrc
directives are updated to allow external resources like fonts and stylesheets from 3rd parties.
In other frameworks, this CSP may be included within a <meta>
tag in your root file, or your root server component.
Real-World Example: Adjusting CSP for Adobe Fonts and Google Fonts
Recently, I ran into an issue where my Hydrogen app refused to load fonts from Google and Adobe. The errors were indicating that my CSP was too restrictive:
Refused to load the stylesheet 'https://p.typekit.net/p.css'... Refused to load the font 'https://fonts.gstatic.com/s/opensans/v40/memtYaGs126MiZpBA-UFUIcVXSCEkx2cmqvXlWqWtU6FxZCJgvAQ.woff2'...
Here was my fix:
Add
https://use.typekit.net
andhttps://p.typekit.net
tostyleSrc
: Adobe Fonts need access to these domains for loading styles and tracking images.Add
https://fonts.gstatic.com
tofontSrc
: Google Fonts are served from this domain, so you need to allow it for your fonts to load properly.Ensure
unsafe-inline
is present instyleSrc
: This is required by Adobe Fonts, as they use inline styles for rendering fonts.
The updated styleSrc
and fontSrc
look like so:
styleSrc: [
"'self'",
"'unsafe-inline'",
'https://cdn.shopify.com',
'https://cdnjs.cloudflare.com',
'https://fonts.googleapis.com',
'https://maxcdn.bootstrapcdn.com',
'https://use.typekit.net',
'https://p.typekit.net',
],
fontSrc: [
"'self'",
'https://fonts.googleapis.com',
'https://maxcdn.bootstrapcdn.com',
'https://use.typekit.net',
'https://fonts.gstatic.com',
],
Once these changes were made, the fonts and styles loaded without issues, and the CSP errors were resolved.
Conclusion
CSP is a powerful tool that helps protect your site from malicious attacks. However, it can be a bit troublesome, especially when using boilerplate that implements it without you knowing.
Keep in mind, making your CSP more permissive can solve loading issues, but it’s important to strike the right balance between security and functionality. Always consider the changes you make to ensure you’re not unintentionally exposing your site to cyber attacks.
Subscribe to my newsletter
Read articles from David Williford directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
David Williford
David Williford
I am a developer from North Carolina. I have always been fascinated by the internet, and scince high school I have been trying to understand the magic behind it all. ECU Computer Science Graduate