TL;DR
I recently deployed a Phoenix LiveView application and encountered the infamous error: “Could not check origin for Phoenix.Socket”. The solution wasn’t immediately obvious, even with Phoenix’s excellent documentation. Therefore, I want to share my discoveries, which required a bit of exploration into the Phoenix codebase.
Phoenix Documentation
The Phoenix documentation advises configuring the Endpoint by setting up the application environment and enabling check_origin to include your application domain:
config :testivator, TestivatorWeb.Endpoint,
http: [port: {:system, "PORT"}],
load_from_system_env: true,
cache_static_manifest: "priv/static/cache_manifest.json",
check_origin: ["//testivator.com"],
force_ssl: [rewrite_on: [:x_forwarded_proto]],
live_view: [signing_salt: System.get_env("AUTHENTICATOR_SEED")]
I thought one more deployment would do it, but the error persisted. Let’s delve deeper into the Phoenix codebase:
defp check_origin_config(handler, endpoint, opts) do
Phoenix.Config.cache(endpoint, {:check_origin, handler}, fn _ ->
check_origin =
case Keyword.get(opts, :check_origin, endpoint.config(:check_origin)) do
origins when is_list(origins) ->
Enum.map(origins, &parse_origin/1)
boolean when is_boolean(boolean) ->
boolean
{module, function, arguments} ->
{module, function, arguments}
:conn ->
:conn
invalid ->
raise ArgumentError,
":check_origin expects a boolean, list of hosts, :conn, or MFA tuple, got: #{inspect(invalid)}"
end
{:cache, check_origin}
end)
end
From this code, we see that :check_origin
is pulled from the configuration (in the case macro) only if it’s not set in the options (opts
). But how are these options determined? They are specified in the socket macro used in the Endpoint:
socket "/live", Phoenix.LiveView.Socket, websocket: [check_origin: :true, connect_info: [session: @session_options]]
Removing check_origin from the socket initialization resolved the issue, allowing the application config’s check_origin setting to take effect.