craft-seomatic icon indicating copy to clipboard operation
craft-seomatic copied to clipboard

Custom nonce values or modifying script tag attributes

Open justinmacleod opened this issue 3 years ago • 9 comments

Question

I need to supply a custom nonce tag that we generate ourselves due to the fact that we can not have multiple CSP headers for script-src being applied. Although multiple headers are valid, they do not get merged together and additional headers are only able to further restrict the policy. As such, we wish to generate the nonce ourselves and apply to our own inline scripts and then also use it with SEOmatic. However, I am unsure how to go about this.

In reviewing old issues, I came across #457 where it is mentioned that the script tag itself can be modified within the Tracking Scripts section, however, this doesn't appear to be the case with GTM. For some, I see the

  1. Is there an ability to override the nonce value?
  2. Is it possible to modify the script output?
  3. Is it possible to obtain just the values defined in the "Script Template" field so we can output on our own?

Cheers Justin

justinmacleod avatar Sep 29 '20 20:09 justinmacleod

You should be able to do this by doing something like (untested):

{% do seomatic.script.get("googleTagManager").nonce("myNonce") %}

...and the rest should just work from there.

As for your third question... if you mean the template that's used to render the GTM script, that's an editable Twig template that is on the SEOmatic -> Tracking Scripts -> Google Tag Manager page

khalwat avatar Sep 30 '20 13:09 khalwat

That totally works. I swore I tried that but I guess not.

For the third question, I was thinking more of the rendered code so we could grab that and inject it into our own script tag but that is totally unnecessary. I will use the nonce param to be able to set our own value which gets replaced by Nginx on output using this approach. This allows for unique values on each request even if the data is cached by SEOmatic or Blitz.

Thanks for your help

justinmacleod avatar Oct 02 '20 00:10 justinmacleod

@khalwat the nonce seems to be picked up by the template cache. Do you know how i can prevent this?

I used it with the craft csp plugin:

{% do seomatic.script.get("googleAnalytics").nonce(cspNonce('script-src')) %}

roelvanhintum avatar Dec 31 '20 10:12 roelvanhintum

@roelvanhintum can you clarify what you mean? What exactly is being cached?

khalwat avatar Dec 31 '20 19:12 khalwat

@khalwat The passed nonce is cached with the script tag, which means the next fetch has an old nonce which doesn't match the CSP headers.

roelvanhintum avatar Jan 04 '21 07:01 roelvanhintum

@roelvanhintum has this been sorted?

khalwat avatar Apr 07 '21 16:04 khalwat

@khalwat unfortunately not. Looking back, i did not explained that i had to do some changes to the analytics script, because it injects a script tag. I've added the nonce also inside the script, but both are cached.

The modified template:

{% if trackingId.value is defined and trackingId.value %}
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.nonce="{{seomatic.script.get("googleAnalytics").nonce}}";a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','{{ analyticsUrl.value }}','ga');
ga('create', '{{ trackingId.value |raw }}', 'auto'{% if linker.value %}, {allowLinker: true}{% endif %});
{% if ipAnonymization.value %}
ga('set', 'anonymizeIp', true);
{% endif %}
{% if displayFeatures.value %}
ga('require', 'displayfeatures');
{% endif %}
{% if ecommerce.value %}
ga('require', 'ecommerce');
{% endif %}
{% if enhancedEcommerce.value %}
ga('require', 'ec');
{% endif %}
{% if enhancedLinkAttribution.value %}
ga('require', 'linkid');
{% endif %}
{% if enhancedLinkAttribution.value %}
ga('require', 'linker');
{% endif %}
{% set pageView = (sendPageView.value and not seomatic.helper.isPreview) %}
{% if pageView %}
ga('send', 'pageview');
{% endif %}
{% endif %}

Output:

<script nonce="246db853055fab1a4a807f4fb14e596ef0c11bbf5b83">(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]\|\|function(){
--
  | (i[r].q=i[r].q\|\|[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  | m=s.getElementsByTagName(o)[0];a.async=1;a.nonce="246db853055fab1a4a807f4fb14e596ef0c11bbf5b83";a.src=g;m.parentNode.insertBefore(a,m)
  | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  | ga('create', 'GA_KEY_GOES_HERE', 'auto');
  | ga('send', 'pageview');
  | </script>

Note that both nonces are cached.

roelvanhintum avatar Apr 08 '21 09:04 roelvanhintum

@roelvanhintum so I can think of ways around this, but I'm starting to believe that instead of a nonce, I think I should be using a hash:

https://content-security-policy.com/hash/

khalwat avatar Apr 08 '21 19:04 khalwat

Good point, guess i need to do something similar for the csp plugin.

roelvanhintum avatar Apr 09 '21 06:04 roelvanhintum