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