Nginx (reverse proxy) + Phoenix でschemeを強制的にhttpsにする
memo: たどり着くのに時間が掛かったで残しておくことにした.
課題
- oauth認証にueberauthのライブラリを使っていた
- callback urlを動的に生成させるときに
Plug.Conn
のschemeを参照している - backend側のサーバーはhttpで動かしたい
- それだと Plug.Connのschemeはhttpなので、https のcallback_urlを生成できない
解決方法
Plug.SSLのプラグを使って強制的にhttpsにschemeを変更しました.
以下、phoenix側のconfigの設定です.
config :my_web, MyWeb.Endpoint, url: [host: "localhost", scheme: "https", port: 443], force_ssl: [ host: nil, rewrite_on: [:x_forwarded_proto] ]
上記のように :force_ssl
の設定を追加します.
(参考はここ Using SSL – Phoenix v1.3.0-rc.2.)
Forcing requests to use SSL:
In many cases, you’ll want to force all incoming requests to use SSL by redirecting http to https. This can be accomplished by setting the :force_ssl option in your endpoint. It expects a list of options which are forwarded to Plug.SSL. By default it sets the “strict-transport-security” header in https requests, forcing browsers to always use https. If an unsafe request (http) is sent, it redirects to the https version using the :host specified in the :url configuration.
記述を読むと、endpointのconfigの:force_sslの設定がPlug.SSLのオプションになるようです.
また、nginx側は以下の設定が必要です.
... location / { ... proxy_set_header X-Forwarded-Proto $scheme; # ここの一行が必要 proxy_pass http://backend/; } ...
これは、Plug.SSLの仕様で :rewrite_on
に設定したヘッダー名がrewriteされるためです.
:rewrite_on - rewrites the scheme to https based on the given headers
なので、そのヘッダーをproxy_set_headerしています.
(apacheなどの他のproxyサーバーでも同様な設定を入れれば期待する挙動になると思います.)
Plug.Connの確認
%Plug.Conn{ adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, before_send: [#Function<0.116269836/1 in Plug.CSRFProtection.call/2>, #Function<4.117387578/1 in Phoenix.Controller.fetch_flash/2>, #Function<0.58261320/1 in Plug.Session.before_send/2>, #Function<1.112466771/1 in Plug.Logger.call/2>, #Function<0.61641163/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>], body_params: %{}, cookies: %{ "_my_web_session_id" => "***" }, halted: false, host: "localhost", method: "GET", owner: #PID<0.467.0>, params: %{}, path_info: [], path_params: %{}, port: 80, private: %{ ***.Router => {[], %{}}, :phoenix_action => :index, :phoenix_controller => ***.PageController, :phoenix_endpoint => ***.Endpoint, :phoenix_flash => %{}, :phoenix_format => "html", :phoenix_layout => {***.LayoutView, :app}, :phoenix_pipelines => [:browser], :phoenix_router => ***.Router, :phoenix_view => ***.PageView, :plug_session => %{"_csrf_token" => "gnjVVSwCXhyfv4pu4rBfVA=="}, :plug_session_fetch => :done }, query_params: %{}, query_string: "", remote_ip: {172, 21, 0, 6}, req_cookies: %{ "_my_web_session_id" => "***" }, req_headers: [ {"host", "localhost"}, {"x-forwarded-for", "172.21.0.1"}, {"x-real-ip", "172.21.0.1"}, {"x-forwarded-proto", "https"}, {"cache-control", "max-age=0"}, {"upgrade-insecure-requests", "1"}, {"user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36"}, {"accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"}, {"referer", "https://localhost/"}, {"accept-encoding", "gzip, deflate, br"}, {"accept-language", "ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7"}, {"cookie", "_my_web_session_id=***"} ], request_path: "/", resp_body: nil, resp_cookies: %{}, resp_headers: [ {"cache-control", "max-age=0, private, must-revalidate"}, {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"}, {"x-content-type-options", "nosniff"}, {"x-download-options", "noopen"}, {"x-permitted-cross-domain-policies", "none"} ], scheme: :https, script_name: [], secret_key_base: :..., state: :unset, status: nil }
scheme:
の部分が https
であることが確認できました.