<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[jxstxn]]></title><description><![CDATA[Hey, I'm jxstxn or Justin. I'm a Flutter Developer who loves Flutter and Dart. In my free time, I'm writing Dart Packages like Middlewares for Shelf or Plugins ]]></description><link>https://jxstxn.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 06 Sep 2026 11:31:44 GMT</lastBuildDate><atom:link href="https://jxstxn.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Elevate Your Flutter Web Hosting with Dart_Frog]]></title><description><![CDATA[Link to the GitHub Repo
Everyone who worked with Flutter Web in a production environment knows this Problem. After a ten-minute deployment, you open the web app and you see a white screen. Then you start to investigate and your Browser tells you the ...]]></description><link>https://jxstxn.dev/elevate-your-flutter-web-hosting-with-dartfrog</link><guid isPermaLink="true">https://jxstxn.dev/elevate-your-flutter-web-hosting-with-dartfrog</guid><category><![CDATA[Dart]]></category><category><![CDATA[Flutter]]></category><category><![CDATA[#dart language]]></category><category><![CDATA[dart_frog]]></category><category><![CDATA[shelf]]></category><dc:creator><![CDATA[Justin Baumann]]></dc:creator><pubDate>Fri, 11 Aug 2023 09:05:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1691681627081/86e3c434-e4bc-48d2-be27-7bf74c2eea36.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://github.com/jxstxn1/hash_demo">Link to the GitHub Repo</a></p>
<p>Everyone who worked with Flutter Web in a production environment knows this Problem. After a ten-minute deployment, you open the web app and you see a white screen. Then you start to investigate and your Browser tells you the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691525897533/5137f6e7-1424-4949-bdf9-352276200e3e.png" alt class="image--center mx-auto" /></p>
<p>This can be very frustrating especially if you start whitelisting scripts to your Content Security Policy and those scripts are requesting resources from somewhere else.</p>
<p><strong>So what's the solution for this?</strong></p>
<p>Content Security Policy errors can only be solved by the web server which is hosting the Web App. There are probably multiple solutions to tackle this problem, but today we are going to take a deep dive into <code>dart_frog</code> and how to utilize it to host our Flutter Web App with some advanced capabilities.</p>
<h3 id="heading-key-topics">Key Topics</h3>
<blockquote>
<p>Note: Content Security Policy will be called CSP in this Blog Post</p>
</blockquote>
<ul>
<li><p>Serve a Flutter Web App with <code>dart_frog</code></p>
</li>
<li><p>Set all recommended security headers with <code>shelf_helmet</code></p>
</li>
<li><p>Create a Content Security Policy that works with CSP2 and 3</p>
</li>
<li><p>Create Hashes for <code>inline-scripts</code> to whitelist them in our Content Security Policy</p>
</li>
</ul>
<h3 id="heading-setting-everything-up">Setting everything up</h3>
<p>We'll start by creating a brand new dart_frog project</p>
<pre><code class="lang-bash">dart_frog create hash_demo
</code></pre>
<p>We can then open our <code>pubpsec.yaml</code> and replace the contents with</p>
<pre><code class="lang-yaml"><span class="hljs-attr">name:</span> <span class="hljs-string">hash_demo</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">An</span> <span class="hljs-string">example</span> <span class="hljs-string">of</span> <span class="hljs-string">how</span> <span class="hljs-string">to</span> <span class="hljs-string">use</span> <span class="hljs-string">the</span> <span class="hljs-string">CSP</span> <span class="hljs-string">Hasher</span> <span class="hljs-string">package</span> <span class="hljs-string">in</span> <span class="hljs-string">combination</span> <span class="hljs-string">with</span> <span class="hljs-string">shelf_helmet.</span>
<span class="hljs-attr">version:</span> <span class="hljs-number">1.0</span><span class="hljs-number">.0</span><span class="hljs-string">+1</span>
<span class="hljs-attr">publish_to:</span> <span class="hljs-string">none</span>

<span class="hljs-attr">environment:</span>
  <span class="hljs-attr">sdk:</span> <span class="hljs-string">"&gt;=3.0.0 &lt;4.0.0"</span>

<span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">csp_hasher:</span> <span class="hljs-string">^1.0.0</span>
  <span class="hljs-attr">dart_frog:</span> <span class="hljs-string">^1.0.0</span>
  <span class="hljs-attr">path:</span> <span class="hljs-string">^1.8.3</span>
  <span class="hljs-attr">shelf_helmet:</span> <span class="hljs-string">^2.1.1</span>

<span class="hljs-attr">dev_dependencies:</span>
  <span class="hljs-attr">mocktail:</span> <span class="hljs-string">^0.3.0</span>
  <span class="hljs-attr">test:</span> <span class="hljs-string">^1.19.2</span>
  <span class="hljs-attr">very_good_analysis:</span> <span class="hljs-string">^5.0.0</span>
</code></pre>
<p>Now we have to create our Flutter project with:<br /><code>flutter create counter --platform web</code></p>
<h3 id="heading-build-and-copy-the-flutter-app">Build and copy the Flutter App</h3>
<p>To build our Flutter App we run in <code>counter</code>:</p>
<pre><code class="lang-bash">flutter build web --web-renderer canvaskit --release --csp
</code></pre>
<p>After the build, we can copy the output from <code>counter/build/web</code> to <code>public/</code><br />To simplify this you can run from <code>hash_demo</code>:</p>
<pre><code class="lang-bash">cp -r counter/build/web/ public/
</code></pre>
<h3 id="heading-serve-flutter-app-at">Serve Flutter App at /</h3>
<p>To serve the <code>index.html</code> file directly at the <code>/</code> location we modify the <code>routes/index.dart</code> file with:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:io'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'package:dart_frog/dart_frog.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:path/path.dart'</span> <span class="hljs-keyword">as</span> path;

Response onRequest(RequestContext context) {
  <span class="hljs-keyword">final</span> file = File(
    path.join(Directory.current.path, <span class="hljs-string">'public'</span>, <span class="hljs-string">'index.html'</span>),
  );
  <span class="hljs-keyword">final</span> indexHtml = file.readAsStringSync();
  <span class="hljs-keyword">return</span> Response(body: indexHtml, headers: {<span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/html'</span>});
}
</code></pre>
<p>To ensure that everything works as expected we will write a test for it.<br />Let's create <code>test/routes/index_test.dart</code> :</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'dart:io'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'package:dart_frog/dart_frog.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:mocktail/mocktail.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'../../routes/index.dart'</span> <span class="hljs-keyword">as</span> route;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MockRequestContext</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">RequestContext</span> </span>{}

<span class="hljs-keyword">final</span> htmlString = File(
  <span class="hljs-string">'<span class="hljs-subst">${Directory.current.path}</span>/public/index.html'</span>,
).readAsStringSync();

<span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'GET /'</span>, () {
    test(<span class="hljs-string">'responds with a 200, an html and the content_type header text/html'</span>,
        () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> context = _MockRequestContext();
      <span class="hljs-keyword">final</span> response = route.onRequest(context);
      expect(response.statusCode, equals(<span class="hljs-number">200</span>));
      expect(
        response.headers,
        equals({
          <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/html'</span>,
          <span class="hljs-string">'content-length'</span>: <span class="hljs-string">'1830'</span>,
        }),
      );
      expect(response.body(), completion(htmlString));
    });
  });
}
</code></pre>
<p>Perfect! We can now serve our Flutter Web App at <code>localhost:8080/</code><br />To test it in a browser you can run:</p>
<pre><code class="lang-bash">dart_frog dev
</code></pre>
<h3 id="heading-creating-our-middleware">Creating our middleware</h3>
<p>We can now create our middleware where most of the magic is happening.<br />To do so run:</p>
<pre><code class="lang-bash">dart_frog new middleware /
</code></pre>
<p>We can now replace the content in the newly created middleware with this:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:dart_frog/dart_frog.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:shelf_helmet/shelf_helmet.dart'</span>;

Handler middleware(Handler handler) {
  <span class="hljs-keyword">return</span> handler.use(requestLogger()).use(
        fromShelfMiddleware(
          helmet(
            options: <span class="hljs-keyword">const</span> HelmetOptions(
              cspOptions: ContentSecurityPolicyOptions.useDefaults(
                directives: {
                  <span class="hljs-string">'script-src'</span>: [
                    <span class="hljs-string">"'strict-dynamic'"</span>,
                    <span class="hljs-string">"'wasm-unsafe-eval'"</span>,
                    <span class="hljs-string">"'self'"</span>,
                    <span class="hljs-string">'blob:'</span>,
                    <span class="hljs-string">'https://unpkg.com/'</span>,
                    <span class="hljs-string">'https://www.gstatic.com/flutter-canvaskit/'</span>,
                  ],
                  <span class="hljs-string">'script-src-elem'</span>: [
                    <span class="hljs-string">"'self'"</span>,
                    <span class="hljs-string">'blob:'</span>,
                    <span class="hljs-string">'https://unpkg.com/'</span>,
                    <span class="hljs-string">'https://www.gstatic.com/flutter-canvaskit/'</span>,
                  ],
                  <span class="hljs-string">'connect-src'</span>: [
                    <span class="hljs-string">"'self'"</span>,
                    <span class="hljs-string">'https://unpkg.com/'</span>,
                    <span class="hljs-string">'https://www.gstatic.com/flutter-canvaskit/'</span>,
                    <span class="hljs-string">'https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf '</span>,
                  ],
                  <span class="hljs-string">'style-src'</span>: [
                    <span class="hljs-string">"'self'"</span>,
                    <span class="hljs-string">'https:'</span>,
                  ],
                  <span class="hljs-string">'require-trusted-types-for'</span>: [<span class="hljs-string">"'script'"</span>],
                },
              ),
            ),
          ),
        ),
      );
}
</code></pre>
<p>We use <code>shelf_helmet</code> to set nearly all security headers. Since we are using a Flutter Web App we have to define some Urls in our CSP. This CSP is working with CSP3 and all previous versions.</p>
<blockquote>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP">You can find a deeper explanation of CSP here</a></p>
<p><a target="_blank" href="https://pub.dev/packages/shelf_helmet">And the documentation of shelf_helmet</a></p>
</blockquote>
<p>We ensure that all recommended headers are set by creating a test for our middleware. So let's create <code>test/routes/_middleware_test.dart</code>:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:dart_frog/dart_frog.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:mocktail/mocktail.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'../../routes/_middleware.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MockRequestContext</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">RequestContext</span> </span>{}

<span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'Middleware'</span>, () {
    test(<span class="hljs-string">'add all required headers'</span>, () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> handler = middleware((context) =&gt; Response());
      <span class="hljs-keyword">final</span> request = Request.<span class="hljs-keyword">get</span>(<span class="hljs-built_in">Uri</span>.parse(<span class="hljs-string">'http://localhost/'</span>));
      <span class="hljs-keyword">final</span> context = _MockRequestContext();

      when(() =&gt; context.request).thenReturn(request);

      <span class="hljs-keyword">final</span> finishedHandler = <span class="hljs-keyword">await</span> handler(context);

      <span class="hljs-keyword">const</span> cspRules =
          <span class="hljs-string">'''script-src 'strict-dynamic' 'wasm-unsafe-eval' 'self' blob: https://unpkg.com/ https://www.gstatic.com/flutter-canvaskit/;script-src-elem 'self' blob: https://unpkg.com/ https://www.gstatic.com/flutter-canvaskit/;connect-src 'self' https://unpkg.com/ https://www.gstatic.com/flutter-canvaskit/ https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf ;style-src 'self' https:;require-trusted-types-for 'script';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'''</span>;
      _expectedHeaders(finishedHandler.headers, cspRules);
    });
  });
}

<span class="hljs-keyword">void</span> _expectedHeaders(<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>&gt; headers, <span class="hljs-built_in">String</span> cspRules) {
  expect(headers[<span class="hljs-string">'content-length'</span>], <span class="hljs-string">'0'</span>);
  expect(headers[<span class="hljs-string">'x-xss-protection'</span>], <span class="hljs-string">'0'</span>);
  expect(headers[<span class="hljs-string">'x-permitted-cross-domain-policies'</span>], <span class="hljs-string">'none'</span>);
  expect(headers[<span class="hljs-string">'x-frame-options'</span>], <span class="hljs-string">'SAMEORIGIN'</span>);
  expect(headers[<span class="hljs-string">'x-download-options'</span>], <span class="hljs-string">'noopen'</span>);
  expect(headers[<span class="hljs-string">'x-dns-prefetch-control'</span>], <span class="hljs-string">'off'</span>);
  expect(headers[<span class="hljs-string">'x-content-type-options'</span>], <span class="hljs-string">'nosniff'</span>);
  expect(
    headers[<span class="hljs-string">'strict-transport-security'</span>],
    <span class="hljs-string">'max-age=15552000; includeSubDomains'</span>,
  );
  expect(headers[<span class="hljs-string">'referrer-policy'</span>], <span class="hljs-string">'no-referrer'</span>);
  expect(headers[<span class="hljs-string">'origin-agent-cluster'</span>], <span class="hljs-string">'?1'</span>);
  expect(headers[<span class="hljs-string">'cross-origin-resource-policy'</span>], <span class="hljs-string">'same-origin'</span>);
  expect(headers[<span class="hljs-string">'cross-origin-opener-policy'</span>], <span class="hljs-string">'same-origin'</span>);
  expect(headers[<span class="hljs-string">'Content-Security-Policy'</span>], cspRules);
}
</code></pre>
<p>In our <code>_expectedHeaders</code> we can see the work of <code>shelf_helmet</code>.</p>
<h3 id="heading-create-hashes-for-inline-scripts-with-the-csphasher-package">Create hashes for <code>inline-scripts</code> with the <code>csp_hasher</code> package</h3>
<p>Since Flutter is using <code>inline-scripts</code> like this:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-comment">// The value below is injected by flutter build, do not touch.</span>
    <span class="hljs-keyword">var</span> serviceWorkerVersion = <span class="hljs-string">"1515245720"</span>;
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>which is causing our Content Security Policy to throw errors and cause our web app to not load we have to generate Hashes for those functions.</p>
<p>Let's start by hashing our <code>inline-scripts</code> in a custom entrypoint at our project root</p>
<pre><code class="lang-dart"><span class="hljs-comment">// custom entrypoint for the app</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">'dart:io'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'package:csp_hasher/csp_hasher.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:dart_frog/dart_frog.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:path/path.dart'</span> <span class="hljs-keyword">as</span> path;

<span class="hljs-built_in">List</span>&lt;CspHash&gt; cspScriptHashes = [];
<span class="hljs-built_in">List</span>&lt;CspHash&gt; cspStyleHashes = [];

Future&lt;HttpServer&gt; run(Handler handler, InternetAddress ip, <span class="hljs-built_in">int</span> port) {
  generateCspHashes();
  <span class="hljs-keyword">return</span> serve(handler, ip, port, poweredByHeader: <span class="hljs-keyword">null</span>);
}

<span class="hljs-comment">/// <span class="markdown">Generates CSP hashes for the scripts and styles in the index.html file</span></span>
<span class="hljs-keyword">void</span> generateCspHashes() {
  <span class="hljs-keyword">final</span> file = File(
    path.join(Directory.current.path, <span class="hljs-string">'public'</span>, <span class="hljs-string">'-.html'</span>),
  );
  <span class="hljs-keyword">if</span> (!file.existsSync()) {
    <span class="hljs-keyword">throw</span> Exception(<span class="hljs-string">'Index Not found\nPlease run build_web.sh first'</span>);
  }

  cspScriptHashes = hashScripts(htmlFile: file);
  cspStyleHashes = hashScripts(
    htmlFile: file,
    hashMode: HashMode.style,
  );
}
</code></pre>
<blockquote>
<p><a target="_blank" href="https://pub.dev/packages/csp_hasher">You can find a link to the <code>csp_hasher</code> package here</a></p>
</blockquote>
<p>This is creating hashes for all our <code>inline-scripts</code> at every hot-reload.</p>
<p>We can now inject the hashes into our Content Security Policy in the <code>_middleware.dart</code> file so that it looks like this:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:dart_frog/dart_frog.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:shelf_helmet/shelf_helmet.dart'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'../main.dart'</span>;

Handler middleware(Handler handler) {
  <span class="hljs-keyword">return</span> handler.use(requestLogger()).use(
        fromShelfMiddleware(
          helmet(
            options: HelmetOptions(
              cspOptions: ContentSecurityPolicyOptions.useDefaults(
                directives: {
                  <span class="hljs-string">'script-src'</span>: [
                    <span class="hljs-string">"'strict-dynamic'"</span>,
                    <span class="hljs-string">"'wasm-unsafe-eval'"</span>,
                    cspScriptHashes.join(<span class="hljs-string">' '</span>).replaceAll(<span class="hljs-string">'"'</span>, <span class="hljs-string">''</span>),
                    <span class="hljs-string">"'self'"</span>,
                    <span class="hljs-string">'blob:'</span>,
                    <span class="hljs-string">'https://unpkg.com/'</span>,
                    <span class="hljs-string">'https://www.gstatic.com/flutter-canvaskit/'</span>,
                  ],
                  <span class="hljs-string">'script-src-elem'</span>: [
                    cspScriptHashes.join(<span class="hljs-string">' '</span>).replaceAll(<span class="hljs-string">'"'</span>, <span class="hljs-string">''</span>),
                    <span class="hljs-string">"'self'"</span>,
                    <span class="hljs-string">'blob:'</span>,
                    <span class="hljs-string">'https://unpkg.com/'</span>,
                    <span class="hljs-string">'https://www.gstatic.com/flutter-canvaskit/'</span>,
                  ],
                  <span class="hljs-string">'connect-src'</span>: [
                    <span class="hljs-string">"'self'"</span>,
                    <span class="hljs-string">'https://unpkg.com/'</span>,
                    <span class="hljs-string">'https://www.gstatic.com/flutter-canvaskit/'</span>,
                    <span class="hljs-string">'https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf '</span>,
                  ],
                  <span class="hljs-string">'style-src'</span>: [
                    <span class="hljs-string">"'self'"</span>,
                    <span class="hljs-string">'https:'</span>,
                    cspStyleHashes.join(<span class="hljs-string">' '</span>).replaceAll(<span class="hljs-string">'"'</span>, <span class="hljs-string">''</span>),
                  ],
                  <span class="hljs-string">'require-trusted-types-for'</span>: [<span class="hljs-string">"'script'"</span>],
                },
              ),
            ),
          ),
        ),
      );
}
</code></pre>
<p>And our updated test is looking like this:</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:csp_hasher/csp_hasher.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:dart_frog/dart_frog.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:mocktail/mocktail.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:test/test.dart'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'../../main.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'../../routes/_middleware.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_MockRequestContext</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Mock</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">RequestContext</span> </span>{}

<span class="hljs-keyword">void</span> main() {
  group(<span class="hljs-string">'Middleware'</span>, () {
    setUp(() {
      cspScriptHashes.addAll([
        CspHash(
          lineNumber: <span class="hljs-number">1</span>,
          hashType: sha256,
          hash: <span class="hljs-string">'abcdef'</span>,
          hashMode: HashMode.script,
        ),
      ]);
      cspStyleHashes.addAll([
        CspHash(
          lineNumber: <span class="hljs-number">1</span>,
          hashType: sha256,
          hash: <span class="hljs-string">'ghijkl'</span>,
          hashMode: HashMode.style,
        ),
      ]);
    });
    test(<span class="hljs-string">'add all required headers'</span>, () <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">final</span> handler = middleware((context) =&gt; Response());
      <span class="hljs-keyword">final</span> request = Request.<span class="hljs-keyword">get</span>(<span class="hljs-built_in">Uri</span>.parse(<span class="hljs-string">'http://localhost/'</span>));
      <span class="hljs-keyword">final</span> context = _MockRequestContext();

      when(() =&gt; context.request).thenReturn(request);

      <span class="hljs-keyword">final</span> finishedHandler = <span class="hljs-keyword">await</span> handler(context);

      <span class="hljs-keyword">const</span> cspRules =
          <span class="hljs-string">'''script-src 'unsafe-inline' 'strict-dynamic' 'wasm-unsafe-eval' 'sha256-abcdef' 'self' blob: https://unpkg.com/ https://www.gstatic.com/flutter-canvaskit/;script-src-elem 'sha256-abcdef' 'self' blob: https://unpkg.com/ https://www.gstatic.com/flutter-canvaskit/;connect-src 'self' https://unpkg.com/ https://www.gstatic.com/flutter-canvaskit/ https://fonts.gstatic.com/s/roboto/v20/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf ;style-src 'self' https: 'sha256-ghijkl';require-trusted-types-for 'script';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'''</span>;
      _expectedHeaders(finishedHandler.headers, cspRules);
    });
  });
}

<span class="hljs-keyword">void</span> _expectedHeaders(<span class="hljs-built_in">Map</span>&lt;<span class="hljs-built_in">String</span>, <span class="hljs-built_in">String</span>&gt; headers, <span class="hljs-built_in">String</span> cspRules) {
  expect(headers[<span class="hljs-string">'content-length'</span>], <span class="hljs-string">'0'</span>);
  expect(headers[<span class="hljs-string">'x-xss-protection'</span>], <span class="hljs-string">'0'</span>);
  expect(headers[<span class="hljs-string">'x-permitted-cross-domain-policies'</span>], <span class="hljs-string">'none'</span>);
  expect(headers[<span class="hljs-string">'x-frame-options'</span>], <span class="hljs-string">'SAMEORIGIN'</span>);
  expect(headers[<span class="hljs-string">'x-download-options'</span>], <span class="hljs-string">'noopen'</span>);
  expect(headers[<span class="hljs-string">'x-dns-prefetch-control'</span>], <span class="hljs-string">'off'</span>);
  expect(headers[<span class="hljs-string">'x-content-type-options'</span>], <span class="hljs-string">'nosniff'</span>);
  expect(
    headers[<span class="hljs-string">'strict-transport-security'</span>],
    <span class="hljs-string">'max-age=15552000; includeSubDomains'</span>,
  );
  expect(headers[<span class="hljs-string">'referrer-policy'</span>], <span class="hljs-string">'no-referrer'</span>);
  expect(headers[<span class="hljs-string">'origin-agent-cluster'</span>], <span class="hljs-string">'?1'</span>);
  expect(headers[<span class="hljs-string">'cross-origin-resource-policy'</span>], <span class="hljs-string">'same-origin'</span>);
  expect(headers[<span class="hljs-string">'cross-origin-opener-policy'</span>], <span class="hljs-string">'same-origin'</span>);
  expect(headers[<span class="hljs-string">'Content-Security-Policy'</span>], cspRules);
}
</code></pre>
<h3 id="heading-conclusion">Conclusion</h3>
<p>By utilizing <code>dart_frog</code> in combination with <code>shelf_helmet</code> and <code>csp_hasher</code> it is quite easy to serve a Flutter Web App with the recommended Security Headers and a very strong <code>Content-Security-Policy</code>. Nevertheless, we can avoid all the hashing by removing all <code>inline-scripts</code> from our <code>index.html</code></p>
<p><a target="_blank" href="https://github.com/jxstxn1/hash_demo">Link to the GitHub Repo</a></p>
]]></content:encoded></item></channel></rss>