Firstly, thank you again for your answers!
Enlightened by your replies, I chose the following approach: Phoenix token sent with secure HttpOnly cookie over HTTPS.
I feel like I’m almost there but I can’t seem to find a solution to my (hopefully) last problem.
Phoenix puts the cookie in the response header as expected and I can see it in Chrome Response Headers, which is good.
However, I can’t seem to send the cookie back to the server with my next call. Maybe you guys will see where I’m missing something, I feel like it’s a detail. Here’s my setup:
Phoenix API running on localhost:4000
Cors plug enabled and working, with this in my endpoint.ex file: plug CORSPlug, origin: ["http://localhost:8080"]
Method responsible for putting the cookie in the response (user controller):
def sign_in(conn, %{"user" => user_params}) do case Accounts.token_sign_in(conn, user_params["username"], user_params["password"]) do {:ok, token, user} -> conn |> Plug.Conn.put_resp_cookie("token", token, http_only: true, secure: true, max_age: 604800) |> render("signed_in.json", user: user) _ -> {:error, :unauthorized} end end
Vue.js app running on localhost:8080
Those 2 methods used to test my authentification:
methods: { signIn: function () { this.axios.post(process.env.SERVER_BASE_URL + '/sign_in', { 'user': { 'username': 'joeystl434', 'password': 'somePassword' } }).then(response => { this.signUpResponse = response }) }, accessSecureData: function () { this.axios.post(process.env.SERVER_BASE_URL + '/access_secure_data').then(response => { this.accessSecureDataResponse = response }) } }
Calls
So, when I call the signIn method, this happens in Chrome:
Preflight
General Request URL: http://localhost:4000/api/sign_in Request Method: OPTIONS Status Code: 204 No Content Remote Address: 127.0.0.1:4000 Referrer Policy: no-referrer-when-downgrade Response Headers access-control-allow-credentials: true access-control-allow-headers: Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since,X-CSRF-Token access-control-allow-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS access-control-allow-origin: http://localhost:8080 access-control-expose-headers: access-control-max-age: 1728000 cache-control: max-age=0, private, must-revalidate content-length: 0 date: Thu, 17 May 2018 15:33:42 GMT server: Cowboy vary: Origin Request Headers Accept: */* Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9,fr;q=0.8 Access-Control-Request-Headers: content-type Access-Control-Request-Method: POST Connection: keep-alive Host: localhost:4000 Origin: http://localhost:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
Actual sign-in (we can see the cookie being set)
General Request URL: http://localhost:4000/api/sign_in Request Method: POST Status Code: 200 OK Remote Address: 127.0.0.1:4000 Referrer Policy: no-referrer-when-downgrade Response Headers access-control-allow-credentials: true access-control-allow-origin: http://localhost:8080 access-control-expose-headers: cache-control: max-age=0, private, must-revalidate content-length: 60 content-type: application/json; charset=utf-8 date: Thu, 17 May 2018 15:33:43 GMT server: Cowboy set-cookie: token=SFMyNTY.g3QAAAACZAAEZGF0YWEBZAAGc2lnbmVkbgYAUUy8bmMB.ZKR0G_urmLdhSQv7h2PbOh5GBBAQXnp3iuMjwYIPO-Q; path=/; expires=Thu, 24 May 2018 15:33:44 GMT; max-age=604800; secure; HttpOnly vary: Origin Request Headers Accept: application/json, text/plain, */* Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9,fr;q=0.8 Connection: keep-alive Content-Length: 60 Content-Type: application/json;charset=UTF-8 Host: localhost:4000 Origin: http://localhost:8080 Referer: http://localhost:8080/ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36 {user: {username: "joeystl434", password: "somePassword"}} user : {username: "joeystl434", password: "somePassword"}
And then, I run the accessSecureData method:
General Request URL: http://localhost:4000/api/access_secure_data Request Method: POST Status Code: 200 OK Remote Address: 127.0.0.1:4000 Referrer Policy: no-referrer-when-downgrade Response Headers access-control-allow-credentials: true access-control-allow-origin: http://localhost:8080 access-control-expose-headers: cache-control: max-age=0, private, must-revalidate content-length: 27 content-type: application/json; charset=utf-8 date: Thu, 17 May 2018 15:35:51 GMT server: Cowboy vary: Origin Request Headers Accept: application/json, text/plain, */* Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9,fr;q=0.8 Connection: keep-alive Content-Length: 0 Host: localhost:4000 Origin: http://localhost:8080 Referer: http://localhost:8080/ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
Response: {"data":"Very secure data"} (as expected because I don’t check if the user is authorized yet)
No cookie, and I don’t understand why.
And when I pry here:
def access_secure_data(conn, _params) do test = Plug.Conn.fetch_cookies(conn) require IEx; IEx.pry conn |> render("secure_data.json", data: "Very secure data") end
I can’t seem to access the cookie.
test =
%Plug.Conn{ adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, before_send: [#Function<1.92707701/1 in Plug.Logger.call/2>], body_params: %{}, cookies: %{}, halted: false, host: "localhost", method: "POST", owner: #PID<0.624.0>, params: %{}, path_info: ["api", "access_secure_data"], path_params: %{}, peer: {{127, 0, 0, 1}, 35589}, port: 4000, private: %{ MyAppWeb.Router => {[], %{}}, :phoenix_action => :access_secure_data, :phoenix_controller => MyAppWeb.UserController, :phoenix_endpoint => MyAppWeb.Endpoint, :phoenix_format => "json", :phoenix_layout => {MyAppWeb.LayoutView, :app}, :phoenix_pipelines => [:api], :phoenix_router => MyAppWeb.Router, :phoenix_view => MyAppWeb.UserView, :plug_session_fetch => #Function<1.45862765/1 in Plug.Session.fetch_session/1> }, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %{}, req_headers: [ {"host", "localhost:4000"}, {"connection", "keep-alive"}, {"content-length", "0"}, {"accept", "application/json, text/plain, */*"}, {"origin", "http://localhost:8080"}, {"user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"}, {"referer", "http://localhost:8080/"}, {"accept-encoding", "gzip, deflate, br"}, {"accept-language", "en-US,en;q=0.9,fr;q=0.8"} ], request_path: "/api/access_secure_data", resp_body: nil, resp_cookies: %{}, resp_headers: [ {"cache-control", "max-age=0, private, must-revalidate"}, {"vary", "Origin"}, {"access-control-allow-origin", "http://localhost:8080"}, {"access-control-expose-headers", ""}, {"access-control-allow-credentials", "true"} ], scheme: :http, script_name: [], secret_key_base: "wEwpD63zVGtA3/JTdl5QBH6aZwE3FLD2gkAQWn6XD4Tfgk4lYlsDOoZHAREvfjoX", state: :unset, status: nil }
conn =
%Plug.Conn{ adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, before_send: [#Function<1.92707701/1 in Plug.Logger.call/2>], body_params: %{}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "POST", owner: #PID<0.624.0>, params: %{}, path_info: ["api", "access_secure_data"], path_params: %{}, peer: {{127, 0, 0, 1}, 35589}, port: 4000, private: %{ MyAppWeb.Router => {[], %{}}, :phoenix_action => :access_secure_data, :phoenix_controller => MyAppWeb.UserController, :phoenix_endpoint => MyAppWeb.Endpoint, :phoenix_format => "json", :phoenix_layout => {MyAppWeb.LayoutView, :app}, :phoenix_pipelines => [:api], :phoenix_router => MyAppWeb.Router, :phoenix_view => MyAppWeb.UserView, :plug_session_fetch => #Function<1.45862765/1 in Plug.Session.fetch_session/1> }, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [ {"host", "localhost:4000"}, {"connection", "keep-alive"}, {"content-length", "0"}, {"accept", "application/json, text/plain, */*"}, {"origin", "http://localhost:8080"}, {"user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36"}, {"referer", "http://localhost:8080/"}, {"accept-encoding", "gzip, deflate, br"}, {"accept-language", "en-US,en;q=0.9,fr;q=0.8"} ], request_path: "/api/access_secure_data", resp_body: nil, resp_cookies: %{}, resp_headers: [ {"cache-control", "max-age=0, private, must-revalidate"}, {"vary", "Origin"}, {"access-control-allow-origin", "http://localhost:8080"}, {"access-control-expose-headers", ""}, {"access-control-allow-credentials", "true"} ], scheme: :http, script_name: [], secret_key_base: "wEwpD63zVGtA3/JTdl5QBH6aZwE3FLD2gkAQWn6XD4Tfgk4lYlsDOoZHAREvfjoX", state: :unset, status: nil }
Any help is always deeply appreciated.
Thank you in advance for your time and expertise <3