7

I have an application that uses JWT authentication with Guardian. When a user signs in, the response contains the jwt in the body. The front-end (which is an SPA) then stores that jwt in localStorage, and attaches it to the Authorization header of every request sent from there on. The server then verifies this using Guardian's built-in verification plug:

pipeline :api do
  plug :accepts, ["json"]
  plug Guardian.Plug.VerifyHeader, realm: "Bearer"
end

I would like to change this so that, instead of storing the JWTs in localStorage (which isn't secure), the server sends them to the front-end as secure cookies (with Secure and HttpOnly settings). I then want Guardian to read the jwt from the cookie, rather than from the Authorization header.

Does Guardian support this functionality?

Here is my SessionController create function:

def create(conn, params) do
  case authenticate(params) do
    {:ok, user} ->
      new_conn = Guardian.Plug.api_sign_in(conn, user, :access)
      jwt = Guardian.Plug.current_token(new_conn)

      new_conn
      |> put_status(:created)
      |> render("show.json", user: user, jwt: jwt)
    :error ->
      conn
      |> put_status(:unauthorized)
      |> render("error.json")
  end
end
Ege Ersoz
  • 6,461
  • 8
  • 34
  • 53
  • A JWT is signed by definition. Why would local storage be less secure than cookie storage?? – D. Walsh Mar 14 '17 at 21:34
  • 2
    Because `localStorage` can be read with JavaScript. This means if malicious JavaScript is injected into the page (e.g. in an XSS attack), the jwt can be stolen and used to access the app. In contrast, `HttpOnly` cookies are only accessible by the browser. – Ege Ersoz Mar 14 '17 at 21:37
  • https://www.owasp.org/index.php/HTML5_Security_Cheat_Sheet#Local_Storage – Ege Ersoz Mar 14 '17 at 21:42
  • And cookies are subject to CSRF, local storage is not. You're robbing Peter to pay Paul. – D. Walsh Mar 14 '17 at 21:42
  • Please read the link I provided above. Specifically, the 6th bullet point. "Do not store session identifiers in local storage as the data is always accesible by JavaScript. Cookies can mitigate this risk using the httpOnly flag." – Ege Ersoz Mar 14 '17 at 21:44
  • Please read this https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid – D. Walsh Mar 14 '17 at 21:47
  • Yes, that blog post is exactly what encouraged me to seek this alternative approach. As noted on Hacker News, there isn't anything wrong with JWTs. The blog post is criticizing specific libraries. https://news.ycombinator.com/item?id=13865459 – Ege Ersoz Mar 14 '17 at 21:52

3 Answers3

1

UPDATE: This is with Guardian 1.0 (which you should use if you can!).

I think you can just do something like this:

defmodule MyApp.SessionController do
  def login(conn, params) do
    # your code to find the user based on auth strategy here
    {:ok, jwt, _} = MyApp.Guardian.encode_and_sign(user)
    conn = put_session(conn, :auth_token, jwt)
    # send your response...
  end
end

Would use HttpOnly and Secure flags on the cookie. Should be fine to store client side this way.

To fetch the user, you may need to write your own little plug, but your MyApp.Guardian module could look something like this:

defmodule MyApp.Guardian do
  use Guardian, otp_app: :my_app
  alias MyApp.Accounts

  def subject_for_token(%Accounts.Human{} = human, _claims) do
    {:ok, to_string(human.id)}
  end
  def subject_for_token(_, _) do
    {:error, :resource_not_found}
  end

  def resource_from_claims(%{"sub" => sub}) do
    case Accounts.find_human(sub) do
      nil -> {:error, :resource_not_found}
      human -> {:ok, human}
    end
  end
  def resource_from_claims(_) do
    {:error, :resource_not_found}
  end
end
cnnrjcbsn
  • 11
  • 1
  • 1
0

Just use encrypted session cookies. JWT tokens aren't intended to be stored locally (in localStorage or in a cookie) because you just give up the benefits of what JWT was intended for.

From your code snippet, it appears you are using JWT tokens as a replacement for session cookies. Why not just use session cookies to begin with?

cpjolicoeur
  • 12,766
  • 7
  • 48
  • 59
  • I've been thinking about that myself. Once I started storing the jwt tokens in a database table to be able to revoke them easily, I realized I had just re-invented the idea of a session - except less secure and nowhere near as battle-tested. You're right that just using session cookies is probably easier! – Ege Ersoz Mar 15 '17 at 21:09
  • 1
    Exactly. JWT is best used as a single use, short lived token. When you start storing them in the server side DB for revocation, and some form of client side storage for reuse, then you have defeated much of their purpose to begin with. Stick with encrypted session cookies and move on to coding up solutions for your actual application instead of trying to find work around to make JWT work as a session replacement mechanism. – cpjolicoeur Mar 16 '17 at 00:33
  • I've been working on getting cookies to work with my SPA all day, and unfortunately I haven't found a solution. Cookies are sent from the server (with `Set-Cookie` header), but the browser never saves them, probably because I'm authenticating over ajax and never doing a 300 redirect. =/ – Ege Ersoz Mar 16 '17 at 00:38
  • The browser definitely is saving them, your ajax calls might not be sending them however. First, if the cookie has `HttpOnly` flag on it, then your JS can't read it period, or second, depending on what Ajax library you may be using, you probably have to enable the sending of cookie data with ajax requests. – cpjolicoeur Mar 16 '17 at 13:35
  • If the browser was saving them, would I not be able to find it in the cookies section? When I look for saved cookies from localhost, nothing is there. – Ege Ersoz Mar 16 '17 at 13:52
  • The last point on this post says HttpOnly cookies can still be sent with HXR requests: https://security.stackexchange.com/questions/53359/are-httponly-cookies-submitted-via-xmlhttprequest-with-withcredentials-true/65469#65469 – Ege Ersoz Mar 16 '17 at 13:53
  • Yes, the cookie will be sent even if it is `HttpOnly`, you just wont be able to access the cookie contents via JavaScript – cpjolicoeur Mar 16 '17 at 17:37
  • I understand that. I'm not trying to access the cookie contents. My issue is that the browser does not save the cookie coming from the server, and therefore there is nothing to include in subsequent requests. – Ege Ersoz Mar 16 '17 at 18:34
  • You'd have to show me the request/response dump to ensure the set cookie data is there – cpjolicoeur Mar 16 '17 at 18:46
  • Sure. I actually posted them in another question, here: http://stackoverflow.com/questions/42822142/unable-to-send-credentials-in-cors-request – Ege Ersoz Mar 16 '17 at 18:57
0

I think you misunderstand how JWTs work. It doesn't matter if it's exposed. Get a JWT on login, and verify on page reload/refresh. It's signed on the back end. The whole point of it is to be able to have an object that you can pass around in the FE without having to expose your SKs

Ian
  • 544
  • 3
  • 16