Confusion about security considerations of the LiveView model

When reading Security considerations — Phoenix LiveView v0.20.2, it mentions:

For example, if you perform user authentication and confirmation on every HTTP request via Plugs, such as this:
plug :ensure_user_authenticated
plug :ensure_user_confirmed

Then the mount/3 callback of your LiveView should execute those same verifications:

def mount(params, %{"user_id" => user_id} = _session, socket) do
  socket = assign(socket, current_user: Accounts.get_user!(user_id))

  socket =
    if socket.assigns.current_user.confirmed_at do
      socket
    else
      redirect(socket, to: "/login")
    end

  {:ok, socket}
end

Question: Should I implement confirmation related logic again in mount/3?

As I know, a liveview page has 2 stages:

  1. HTTP request
  2. stateful connection

In the first stage, if the user is not confirmed, the process of page will be blocked by plug :ensure_user_confirmed, stateful connection will never be established. If I think in this way, It seems that the sentence “Then the mount/3 callback of your LiveView should execute those same verifications” is wrong.

Am I right in thinking so? Can you guys give me some tips? :wink:

Imagine you navigate from page A to page B through live navigation. In that case there’s no HTTP request, as everything goes through the stateful connection.

There is always be XHR HTTP request if we change the page (and load different LiveView module), even with special LiveView link. It runs though standard pipeline and authenticates new LiveView to load with response something like

<div data-phx-session="encoded-session-data"></div>

Then the next page will load through the active WebSocket.

I think you’re quite right because in order to load the LiveView we first get the new session token and then ask to load it through the web socket connection providing that token.

1 Like

Doesn’t live view check the session token under the hood for us?

The problem here is not the lack or usage of the plug pipeline for switching pages. The problem is (re-)connects. Once a user has valid encoded session data this can be used to connect to a page via websockets. This connection might be way later than the time the session data was encoded in the plug pipeline. Auth details might have changed in the meantime.

1 Like

Indeed, it is a possible scenario.

Also take the case of confirmation as an example, when navigating from page A to page B, there are two possible scenarios :

  • page A and page B are implemented in different live view, the redirect operation will be triggered by live_redirect or push_redirect. In this case, it will go through those two stages, too. (not the case that you said)
  • page A to page B are implemented in the same live view, the redirect operation will be triggered by live_patch or push_patch. This is the case which doesn’t care about HTTP request. (the case that you said)

I can’t understand why the connection might be later than the session data was encoded in the plug pipeline. The order I expected is:

  1. plug pipeline encode CSRF token into normal HTTP request
  2. frontend js bundle try to establish a websocket connection with the CSRF token which is extract from HTML, like this:
const csrfToken = document
  .querySelector("meta[name='csrf-token']")
  .getAttribute('content')

const liveSocket = new LiveSocket('/live', Socket, {
  params: { _csrf_token: csrfToken },
})

If live view works in this way, it should never encounter above situation.

  1. Connection lost
  2. Half an hour goes by
  3. The client re-establishes connection using the same CSRF token and the same encoded session data.

Unless your session timeout is exceptionally short this can happen.

The problem you described above is about staled auth info which is stored in session, right?

If I use the Plug.Session.COOKIE session store, the session data will be saved in browser’s cookie. If I save permission-sensitive data in session, it is indeed not safe.

But, if I just store a user_id which will not change in the whole life of application, then there will be no staled auth info which are stored in session. In this way, will I encounter the problem you described above?

The issue is that a CSRF token is not a nonce, aka not limited to be used only once.

So, unless you make some changes in your backend to discard all attempts to use the CSRF token more then 1 time, then it can be used multiple times, therefore open to be abused in replayed requests and/or to establish connections from other sources.

1 Like

Any information you derived from the user in the plug pipeline will become stale though. :confirmed_at might not be the best example as it’s hardly ever revoked. Imagine a boolean like “banned_at” or any other authorization, which can be revoked.

It can happen right after mount action performed with active session as well, no need to wait for 30 minutes and re-use old session. Does this make all applications which perform authentication check on mount vulnerable to this case?

This connection might be way later than the time the session data was encoded in the plug pipeline. Auth details might have changed in the meantime.

Auth details can be changed right after mount action as well.

After changing auth details of a user you can broadcast a disconnect to their socket, forcing a reconnect and a new mount.

2 Likes