I added a "Sign In with Google" button to my test web app.
I tried to add a CSP following the advise at Setup instructions > Content Security Policy:
script-src https://accounts.google.com/gsi/client;frame-src https://accounts.google.com/gsi/;connect-src https://accounts.google.com/gsi/;style-src https://accounts.google.com/gsi/style;report-uri https://localhost.rubenlaguna.com/csp-report;default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src-attr 'none';upgrade-insecure-requests
The button displays ok, but I see a CSP violation for style-src-elem
reported:
csp-report {
'csp-report': {
'document-uri': 'https://localhost.rubenlaguna.com/',
referrer: '',
'violated-directive': 'style-src-elem',
'effective-directive': 'style-src-elem',
'original-policy': "script-src https://accounts.google.com/gsi/client;frame-src https://accounts.google.com/gsi/;connect-src https://accounts.google.com/gsi/;style-src https://accounts.google.com/gsi/style;report-uri https://localhost.rubenlaguna.com/csp-report;default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src-attr 'none';upgrade-insecure-requests",
disposition: 'enforce',
'blocked-uri': 'inline',
'line-number': 383,
'column-number': 8676,
'source-file': 'https://accounts.google.com/gsi/client',
'status-code': 200,
'script-sample': ''
}
}
I can get rid of the violation by adding 'unsafe-inline'
to the style-src
. But I was wondering if it actually required or not, or if there is a way to get rid of this violation by changing the CSP?
It's possible to avoid the unsafe-inline
completely by using nonce
(See CSP nonce, style-src nonce)
The CSP violation happens because the google client library (https://accounts.google.com/gsi/client) creates some inline styles. Fortunately, the client library will "pass-through" the document.currentScript.nonce
to the inline style. If the inline style has a nonce then you can suppress the CSP violation by allowing 'nonce-xxxx'
First, you need to pass a nonce
value to the google client library like this
<script nonce="xxx123" src="https://accounts.google.com/gsi/client" async defer></script>
the nonce
should be random value that changes in every reload (see examples below on how to achieve this with express.js)
Then, you add the following in your CSP
style-src https://accounts.google.com/gsi/ 'nonce-xxx123';
If you are using express.js to generate your pages you could have a middleware like this to generate the nonce
, store it in the request and set the Content-Security-Policy
header:
app.use((req, res, next) => { // express middleware
req.nonce = crypto.randomBytes(16).toString("base64url"); // we'll use when rendering the HTML
const csp = [
`script-src https://accounts.google.com/gsi/client;`,
`frame-src https://accounts.google.com/gsi/;`,
`connect-src https://accounts.google.com/gsi/;`,
`style-src https://accounts.google.com/gsi/style 'nonce-${req.nonce}';`, // NOTE: allow the nonce
`report-uri ${process.env.SITE_ADDRESS}/csp-report;`,
"default-src 'self';",
"base-uri 'self';",
"font-src 'self' https: data:;",
"form-action 'self';",
"frame-ancestors 'self';",
"img-src 'self' data:;",
"object-src 'none';",
"script-src-attr 'none';",
"upgrade-insecure-requests",
];
const csp1 = csp.join("");
res.header("Content-Security-Policy", csp1);
next();
});
Then the req.nonce
can be used to render the page (using EJS templates):
app.get("/", (req, res, next) => {
res.render("login", {
google_client_id: process.env.GOOGLE_CLIENT_ID,
site_address: process.env.SITE_ADDRESS,
nonce: req.nonce, //NOTE: the EJS template needs this value
});
});
that will render the template at views/index.ejs
using <script nonce="<%=nonce%>" ...
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>HTML 5 Boilerplate</title>
<script nonce="<%=nonce%>" src="https://accounts.google.com/gsi/client" async defer></script>
</head>
<body>
<div>
<div
id="g_id_onload"
data-client_id="<%=google_client_id%>"
data-context="signin"
data-ux_mode="redirect"
data-login_uri="<%=site_address%>/sign-in-with-google-callback"
data-itp_support="true"
></div>
<div
class="g_id_signin"
data-type="standard"
data-shape="rectangular"
data-theme="outline"
data-text="signin_with"
data-size="large"
data-logo_alignment="left"
></div>
</div>
</body>
</html>