I'm trying to follow this tutorial for adding a Content-Security-Policy (CSP) header to the live dashboard route in Phoenix. It works fine until I use a Map
as the csp_nonce_assign_key
value instead of an atom
.
Maps seem to be supported as per the documentation and it does seem to work when I set the value in my router.ex
file like this:
live_dashboard "/dashboard",
csp_nonce_assign_key: %{
img: generate_nonce(),
style: generate_nonce(),
script: generate_nonce(),
}
However, it doesn't work if I use a Plug like this:
# router.ex
live_dashboard "/dashboard",
csp_nonce_assign_key: :csp_nonce_value
# my_plug.ex
def call(conn, _opts) do
conn
|> assign(:csp_nonce_value, %{
img: generate_nonce(),
style: generate_nonce(),
script: generate_nonce(),
})
end
When I use the Plug
version I get the following error: protocol Phoenix.HTML.Safe not implemented for %{img: "fMIOCwnmMfsaOA", script: "m1oNHieWGoYMfw", style: "9EDcaW6JlgcfxQ"} of type Map.
What I don't understand is why the same error doesn't happen in the first version. PS. I'm new to Elixir, so I'm guessing there's something super obvious I'm missing here.
I have updated the blog post with an answer to your question. To make it short, you need to create 3 different assigns in the Plug, containing the 3 nonces.
Then in the router, you give the name of these 3 assigns, not the nonce values.
in the Plug:
def call(conn, _opts) do
# a random string is generated
nonce_1 = generate_nonce()
nonce_2 = generate_nonce()
nonce_3 = generate_nonce()
csp_headers = csp_headers(Application.fetch_env!(:my_app, :app_env), nonce_1, nonce_2, nonce_3)
conn
# the nonce is saved in the connection assigns
|> Plug.Conn.assign(:img_src_nonce, nonce_1)
|> Plug.Conn.assign(:img_style_nonce, nonce_2)
|> Plug.Conn.assign(:img_script_nonce, nonce_3)
|> Phoenix.Controller.put_secure_browser_headers(csp_headers)
end
In the router file
live_dashboard("/phoenix-dashboard",
metrics: Transport.PhoenixDashboardTelemetry,
csp_nonce_assign_key: %{
img: :img_src_nonce,
style: :img_style_nonce,
script: :img_script_nonce,
})