How to pass `style_nonce` or `img_nonce` to socket.assigns, to make inline style work with CSP?

Hey Guys,

I have a peculiar requirement, i.e. I want to use inline style or maybe a style directive, for styling source code.

I prefer using Chroma for source code highlighting as it has support for lots of languages & has lots of styling.

Here’s the demo: Chroma Playground (v2.8.0)


Now that I’m moving my blog to Elixir & Phoenix. How do I make the inline style work with CSP?

I’m unable to pass the style_nonce to the blog live view!!


Here’s the CSP Header Plug:

defmodule DerpyToolsWeb.Plugs.CustomSecureBrowserHeaders do
  def init(options), do: options

  def call(conn, _opts) do
    img_nonce = generate_nonce()
    style_nonce = generate_nonce()
    script_nonce = generate_nonce()

    csp_headers =
      csp_headers(
        img_nonce,
        script_nonce
      )

    conn
    |> Plug.Conn.assign(:csp_img_nonce, img_nonce)
    |> Plug.Conn.assign(:csp_style_nonce, style_nonce)
    |> Plug.Conn.assign(:csp_script_nonce, script_nonce)
    |> Phoenix.Controller.put_secure_browser_headers(csp_headers)
  end

  def csp_headers(img_nonce, script_nonce) do
    csp_content =
      case Application.fetch_env!(:derpy_tools, :env) do
        # :prod -> connect-src wss://derpytools.com wss://www.derpytools.com;
        # :stg -> connect-src wss://derpytools.site wss://www.derpytools.site;
        :dev ->
          """
          default-src 'self' data: https://*;
          font-src 'self' data: https://*;
          style-src 'self' data: https://* 'unsafe-inline';
          img-src 'self' data: https://* 'nonce-#{img_nonce}';
          script-src 'self' https://* 'nonce-#{script_nonce}';
          connect-src wss://derpytools.com wss://www.derpytools.com wss://derpytools.site wss://www.derpytools.site wss://localhost:* ws://localhost:*;
          """

        _ ->
          nil
      end

    case csp_content do
      nil -> %{}
      csp_content -> %{"content-security-policy" => csp_content |> String.replace("\n", "")}
    end
  end

  defp generate_nonce(size \\ 10),
    do: size |> :crypto.strong_rand_bytes() |> Base.url_encode64(padding: false)
end

Here’s how I use it:

<script nonce={assigns[:csp_script_nonce]}>
  localStorage.getItem("dark_mode") === "true" && document.documentElement.classList.add("dark");
</script>

I tried to pass it the way csrf_token is passed, using get_connect_params, but that doesn’t work as the value is available only when socket is connected.

Is there another way?

Or should I stick to global styling for code highlighting?

1 Like

Figured it out:

conn
    |> Plug.Conn.put_session(:img_nonce, img_nonce)
    |> Plug.Conn.put_session(:style_nonce, style_nonce)
    |> Plug.Conn.put_session(:script_nonce, script_nonce)
    |> Plug.Conn.assign(:img_nonce, img_nonce)
    |> Plug.Conn.assign(:style_nonce, style_nonce)
    |> Plug.Conn.assign(:script_nonce, script_nonce)
    |> Phoenix.Controller.put_secure_browser_headers(csp_headers)

Then those values can be read from session in on_mount.

Image nonce doesn’t work though:

image

I thought that I will be able to allow some images from sites by using a nonce in img tag, but it didn’t work.

Anyone uses nonce with images? Or you allow all images in CSP?

1 Like