I talk about implementing common headers in your web app to increase its security.
Headers
Access-Control-Allow-Origin
: Defines domains that can access your content. MDN link.Permissions-Policy
: Defines permissions your web app can request for. W3C link.Referrer-Policy
: Defines how much information you share with link B about link A when you go from link A to link B. MDN link.Strict-Transport-Security
: Defines whether or nothttps://
should be compulsory when accessing your web app. MDN link.X-Content-Type-Options
: Mandates content to be of the type defined. MDN link.X-Frame-Options
: Defines whether or not your web app can be loaded within an iframe or other embed elements. MDN link.X-XSS-Protection
: Defines Cross-Site Scripting filter level. MDN link.
Examples
Say you have a web app hosted on https://example.com:
const HEADERS = [
{
key: 'Access-Control-Allow-Origin',
value: 'https://example.com'
},
{
key: 'Permissions-Policy',
value: 'autoplay=(), camera=(), fullscreen=(), geolocation=(), microphone=()'
},
{
key: 'Referrer-Policy',
value: 'no-referrer'
},
{
key: 'Strict-Transport-Security',
value: 'max-age=63072000; includeSubDomains; preload'
},
{
key: 'X-Content-Type-Options',
value: 'nosniff'
},
{
key: 'X-Frame-Options',
value: 'DENY'
},
{
key: 'X-XSS-protection',
value: '1; mode=block'
}
]
On Next.js apps deployed on Vercel, you set the headers in next.config.js
like so:
// next.config.js
const headers = async () => [
{
source: '/(.*)',
headers: // Paste HEADERS here
}
];
module.exports = {
headers
};
On SPAs deployed on Firebase Hosting, you set the headers in firebase.json
like so:
// firebase.json
{
"hosting": {
"headers": [
{
"source": "**",
"headers": // Paste HEADERS here
}
]
}
}
You may want to change the value of Access-Control-Allow-Origin
header to allow CORS.
You may also want to change the value of Permissions-Policy
header based on permissions you intend to ask for.
CSP: The King of Security Headers
Content-Security-Policy
(MDN link) is an extremely powerful header since it combines various policies, allowing you to define the security policy of your web app in a fine-grained fashion. It is difficult to get right in the first attempt, so it behoves to not be included in the “low-hanging fruits” list above — but you shouldn’t skip it. It guarantees visitors have a safe time on your web app.
To build the tightest CSP for your web app, it is recommended to start with locked-down policies and open them up as you proceed. Chrome is very descriptive about CSP violations so it is easy to know which policy to open-up next. It is essential to test all features of your web app to ensure there are no violations. Use a new Incognito window every time you make changes to any policy to ensure you aren’t working with cached results.
It is common to disable CSP during development to allow tools to work properly. You can use an environment variable to disable CSP during development. Here’s an example of a CSP header configuration of a Next.js app that uses Google Fonts (note the usage of environment variable NEXT_PUBLIC_IS_PRODUCTION
):
// next.config.js
const HEADERS = [
...,
{
key: 'Content-Security-Policy',
value: process.env.NEXT_PUBLIC_IS_PRODUCTION === 'true' ? "connect-src 'self'; default-src 'self'; font-src https://fonts.gstatic.com; img-src 'self' data:; manifest-src 'self'; object-src 'none'; script-src 'self'; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; upgrade-insecure-requests;" : ''
},
...
]
You can evaluate the strictness of your policy using Google’s CSP Evaluator.
Quantifying the Result
Mozilla Observatory is to Web Security, what Lighthouse is to Web Performance. It rates the security of your web app and provides suggestions to improve that rating. It also integrates 3rd party scanners like Security Headers so you can have multiple opinions.
Was I able to help you increase your web app’s security score? Let me know on my Twitter. Cheers!
Top comments (2)
Thanks for this. It is quite informative
Very nice article