Disqus on Ghost with CSP

Disqus on Ghost with CSP

Having moved my blog to Ghost I lost a comments section for each post. This was functionality which was built into Blogger (my previous blogging platform) as a standard option, and I wanted to get it back

The Ghost platform focuses primarily on the ease of creating the article, and things like seeing how many page views I get or a comments section arn't built-in to the core product. Ghost is very powerful however and fully customizable and as I have the ability to customize the site how I chose, I could add my own like Disqus.

Disqus is a common commenting system I've seen on several sites, and they have an ad-free cost-free pricing option which I feel would be right for this blog (BTW it may come as a surprise but I get around 49,349[1] daily page views so I am OK with that restriction!)

disqus-addfree-costfree

I wanted to give it a go so I went about adding it to this blog, while also maintaining the Content-Security-Policy for the site at the same time.

Very helpfully, Ghost have a simple guide stepping you through adding Disqus. As I already had a Disqus account (as I'd commented on other sites which used it) I didn't need to a Disqus account, and as I self-host I could directly edit the post.hbs file.

I did tweak one part which was the Universal Embed Code from Disqus. As I have a HTTP response header enforcing Content Security Policy (CSP) I needed to actually put the following code in my post.hbs file [2]:-

<div id="disqus_thread"></div>

<script>

/**
*  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
*  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/

var disqus_config = function () {
this.page.url = document.getElementById("disqus_page_url").getAttribute("data");  // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = document.getElementById("disqus_post_identifier").getAttribute("data"); // Replace PAGE_IDENTIFIER with your page's unique identifier variable
};

(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://tras2.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<div id="disqus_post_identifier" data="ghost-{{comment_id}}"></div>
<div id="disqus_page_url" data="{{url absolute="true"}}"></div>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>

See the difference? The small changes are

  • the this.page.identifier = document.getElementById("disqus_post_identifier").getAttribute("data"); line which (as described by Disqus) should be this.page.identifier = "ghost-{{comment_id}}"
  • the this.page.url = document.getElementById("disqus_page_url").getAttribute("data"); line which should be this.page.url = "{{url absolute="true"}}"

In both cases, having it be the latter means the code in the <script></script> block is different for every page as the ghost-{{comment_id}} & {{url absolute="true"}} values generated are different for every page which in-turn means the generated script hash never matches a SHA256 hash listed in the site's CSP, and so the browser blocks Disqus from loading. Having it be the former keeps the script block static and it still works as the script gets the Ghost generated identifiers are got from the HTML elements that's identified by the Id. These elements can be put anywhere within the between the opening {{#post}} and closing {{/post}} lines, but I put it in a <div> tag just before the <noscript> line at the end of it (look again toward the end of the code block above)[2:1]. The upshot is that on page load, the this.page.url and this.page.identifier values are got from the dynamically generated <div> tags, and not from a dynamically generated item within the <script> block.

As the <script> block now never changes, I can add a valid sha256-... element to the script-src part of my content security policy and continue to ensure that nothing extra gets loaded into my page that I don't want.

For reference, the additional extra items in my CSP to allow for Disqus are:-
"img-src c.disquscdn.com referrer.disqus.com; script-src 'sha256-...sha256 hash of the disqus script block...' c.disquscdn.com disqus.com tras2.disqus.com; style-src c.disquscdn.com";

This was added to /etc/nginx/sites-enabled/<site>-ssl.conf file at the very top as a add_header line as shown https://ghostpi.pro/secure-your-ghost-blog/


  1. I may have over estimated by 49,340 views ↩︎

  2. A tip of the hat to Troy Hunt as he'd done the same on his site. I only worked this out by viewing the page source on a post to figure out how he embedded Disqus while maintaining a strong CSP ↩︎ ↩︎