jstlroot

jstlroot

Hello y’all!

I’m very new to the Elixir/Phoenix beauty but very excited to learn more!

I’m building a Phoenix json API with a (separate) Vue.js SPA. I’m at the point where I need to build a secured authentication system as my app will be used in production eventually.

I read a lot on the subject (spent 2 full days on this alone) and I like the idea of restful stateless sessions using JWT. My main concern is that although it seems to be the preferred approach, sending JWT directly as a json response and storing it in HTML5 local storage is very unsafe and shouldn’t be done at all.

This post enlightened me a lot on the subject: Randall Degges - Please Stop Using Local Storage

Now, the best approach seems to be using http only cookies. The post I found the most interesting on the subject and close to my needs is this one: https://medium.com/lightrail/getting-token-authentication-right-in-a-stateless-single-page-application-57d0c6474e3

I’m sharing those links should anyone be interested in reading them don’t spend 2 days searching for them :smiley:

Now my question : Is there (I’m sure there is) a way to send, as the last post I shared explains, 2 cookies for user authentication/session, without storing the session on the server?

I have a working Guardian setup which generates JWT successfully and sends it to my client side in a response (the approach I’d like to avoid using). I just need to find a way to

  1. Split the JWT
  2. Send the 2 cookies to the client, one of which should be http only

I tried to find a solution in the online documentations and can’t seem to find the best way to do it. Speaking of documentation, is there a place where Phoenix 1.3 is fully documented? Hexdocs always seems incomplete and, in this case, doesn’t seem to cover sessions. Overview — Phoenix v1.8.8 The Programming Phoenix 1.0 book doesn’t help much either.

Thank’s for any help! <3

Showing Posts 18 to 9

jstlroot

jstlroot OP

Yep, makes sense.

Your idea of making distinction between original and refreshed tokens is interesting, thank you for sharing it!

Don’t hesitate to poke me as needed or to share any other idea. :smile:

acrolink

acrolink

Would be interesting to see what the optimal solution would look like :slight_smile: If the client side is a pure JS application then it is possible to send back to the client application at authentication time (sign in) along with the cookie the value of token expiration (e.g. {"expires_at": unix_time}). A script running at fixed intervals on the client-side or on page navigation checks if there is say less than 24 hours to expiration. If so, a background call to the server is made which triggers the sever to issue a new cookie with new expiration time. Good to make distinction between original token and refreshed tokens since some actions may require providing username and password again.

jstlroot

jstlroot OP

Hi @acrolink

I was out for a couple of days, hence the delay, I’m sorry.

To be honest, I haven’t yet decided how the session expiration will work for my app. What I’d like is for the session to expire after a week of inactivity for the account. At least, that’s what I think my customers will find appropriate.

Many options seem possible. One would be to refresh the token and overwrite the cookie at each request, which shouldn’t be too much of a drag for the server as this process seems very light. Another option would be to be less aggressive about it and go for daily refreshes (first request of the day refreshes the token and cookie).

Another more lazy way would be to set the expiration 2 weeks after cookie/token creation and refresh them when there’s less than one week remaining. The only way this option would make sense to go for is to save server load, which I think is not really an issue, so I most likely won’t go for this one.

I’d be interested to know our thoughts on the subject!

Ah, and yes, I think the cookie and token should have the same expiration value as the behavior is pretty much the same should either of them expire before the other anyway (user needing to log back in).

Cheers! :beers:

acrolink

acrolink

@jstlroot, glad to hear it is working now. I have already implemented your ideas (in regard to secure cookie storage for the token) using Phauxth library. I want to ask: what max_age do you use for the token/cookie expiration (should it be same value)? and do you implement some refresh token mechanism server or client side (for soon to get expired tokens)? Thank you.

jstlroot

jstlroot OP

Oh my god I found it…

I knew it was gonna be stupid.

put_resp_cookie(conn, key, value, opts \\ [])

Options

  • :domain - the domain the cookie applies to
  • :max_age - the cookie max-age, in seconds. Providing a value for this option will set both the max-age and expires cookie attributes
  • :path - the path the cookie applies to
  • :http_only - when false, the cookie is accessible beyond http
  • :secure - if the cookie must be sent only over https. Defaults to true when the connection is https
  • :extra - string to append to cookie. Use this to take advantage of non-standard cookie attributes.

I had :secure forced to true but I’m not using HTTPS in my dev environment.

I hope this discussion will help others determine how they want to implement token authentification with Phoenix and Vue.js :slight_smile:

Thank you all again for your time and support <3

OvermindDL1

OvermindDL1

Very very true. Plus if you encode, say, a JWT token into a web page (as is common with ‘any’ token for phoenix websockets sadly) then a page can quite literally just read another page and parse out information from it using your auth’d user (there are hazards they have to work around to do that, but there are still ways).

jstlroot

jstlroot OP

Yes.

  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: false, secure: true, max_age: 604800)
        |> render("signed_in.json", user: user)
      _ ->
        {:error, :unauthorized}
    end
  end

The access_secure_data gives me exactly the same output:

%Plug.Conn{
  adapter: {Plug.Adapters.Cowboy.Conn, :...},
  assigns: %{},
  before_send: [#Function<1.92707701/1 in Plug.Logger.call/2>],
  body_params: %{"withCredentials" => true},
  cookies: %{},
  halted: false,
  host: "localhost",
  method: "POST",
  owner: #PID<0.799.0>,
  params: %{"withCredentials" => true},
  path_info: ["api", "access_secure_data"],
  path_params: %{},
  peer: {{127, 0, 0, 1}, 47456},
  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", "24"},
    {"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"},
    {"content-type", "application/json;charset=UTF-8"},
    {"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
}

Maybe something to add to the discussion, using Postman never helps either. i.e. In this case, it sees my cookie being set as expected on the sign_in call but the access_secure_data call is not sending back my cookie to the server even though I can see it in Postman…

amnu3387

amnu3387

Have you tried turning off HttpOnly (in this case not setting it to true) when setting the cookie?

jstlroot

jstlroot OP

Of course! I’m no expert but what I learned from reading articles and blog posts for 3 days is that giving javascript access to an authentication token (jwt or phoenix) is never a good idea because of cross-site scripting attacks.

To quote this article: Randall Degges - Please Stop Using Local Storage

If an attacker can run JavaScript on your website, they can retrieve all the data you’ve stored in local storage and send it off to their own domain. This means anything sensitive you’ve got in local storage (like a user’s session data) can be compromised.

If an attacker can get a copy of your JWT, they can make requests to the website on your behalf and you will never know. Treat your JWTs like you would a credit card number or password: don’t ever store them in local storage.

It seems like using the request header body to send the token also gives javascript access to it. There might be a way to avoid this that I don’t know of? If so, it could be a solution.

HttpOnly cookies are isolated from javascript and cannot be read by it, making authentication token stealing XSS attacks more complex, if not impossible. Cookies are open to other types of attacks but this is mitigated by the use of HTTPS all over. At least that’s what I understood.

Maybe there are some things I don’t quite understand yet and if you feel like it’s the case, please point it out so I can avoid doing dangerous mistakes. :smile:

acrolink

acrolink

@jstlroot, would you mind explaining how does using a cookie provide better security than sending the JWT token in the request header body?

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews