Search code examples
content-security-policygoogle-identity

Does the "Sign In with Google" button require CSP style-src 'unsafe-inline'?


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?


Solution

  • 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>