Hanami 1.3 > Hanami 2.2 : tips & notes about my journey

Use case #6: tweaking CSP directives to make AlpineJS work

In my previous use case, I included AlpineJS into my project; but it didn’t work at first try. Indeed, this library needs a few “unsafe” settings in order to work. CSP headers are used to notify the browser that you allow this kind of manipulation.

I didn’t find an official way to modify CSP per slice in the framework, so for the moment I decided to write my own “middleware” to do that. (Note: I know it’s not a real Rack middleware, just an included module with a before hook, but it’s the spirit is the same so that’s why I named it that way. You could definitely do that will a real Rack middleware BTW)

Here is the code of my middleware:

# slices/my_slice/middlewares/set_csp_directives.rb

# auto_register: false

module MySlice
  module Middlewares
    module SetCSPDirectives
     
      def self.included(action_class)
        action_class.before :set_custom_csp_directives
      end

      def set_custom_csp_directives(request, response)
        custom_directives = DEFAULT_CSP_DIRECTIVES.merge(
          "script-src"     => "'self' 'unsafe-eval'",
          "style-src-elem" => "'self' 'unsafe-inline'",
        )

        response.headers["Content-Security-Policy"] = custom_directives.map do |key, value|
          "#{key} #{value}"
        end.join(";")
      end

      # In my real app this constant is defined for my whole project, putting it here for the example
      DEFAULT_CSP_DIRECTIVES = {
        "base-uri"        => "'self'",
        "child-src"       => "'self'",
        "connect-src"     => "'self'",
        "default-src"     => "'none'",
        "font-src"        => "'self'",
        "form-action"     => "'self'",
        "frame-ancestors" => "'self'",
        "frame-src"       => "'self'",
        "img-src"         => "'self' https: data:",
        "media-src"       => "'self'",
        "object-src"      => "'none'",
        "script-src"      => "'self'",
        "style-src"       => "'self' 'unsafe-inline' https:",
        "style-src-elem"  => "'self'",
      }

    end
  end
end

I can then include it in my stack, like this:

module MySlice
  class Action < MyApp::Action

    include MySlice::Middlewares::SetCSPDirectives

    include Deps[
      "logger",
    ]

  end
end

The browser will then receive these patched CSP on each request, allowing AlpineJS to work properly. :tada:

1 Like