Why is :current_user is always nil in conn

I handle session management in my phoenix controllers by setting the :current_user of the connection:

def create(conn, params) do
    case Hestia.Session.login(params, Hestia.Repo) do
      {:ok, user} ->
        conn
        |> put_session(:current_user, user)
      :error ->
        {:error, :unauthorized}
    end
  end

When accessing the webpage via a browser such as Safari or Chrome, the session seems to stay for weeks without need for the user to re-login.

When accessing the server via the mobile app (using Alamofire or SDWebImage for instance) for each http-request a login is necessary. I.e. the session seems to never be re-used.

I am trying to understand what happens and I am no expert. What does the browser do differently than the mobile libraries and is it possible and advisable to replicate what the browser does? Currently, I pass the user and password on every http-request. This means that the server looks up the user and hits the database for every request. This is probably suboptimal. What do you think?

Best regards and thank you in advance for your time and help !

If you are using the default phoenix/plug sessions, then the default session store would be cookie. Can you verify the requests from coming from both the browsers and mobile app have the session cookie? You can inspect the conn.cookies (shows both req/res cookies) & conn.req_cookies to see the cookies for each request. If mobile requests don’t have cookies you might want to refer the docs for Alamofire/SDWebImage for storing/sending cookies.

This is just my guess of what could be happening, its not clear how your authentication is implemented.

Thank you for your reply. I think the session cookie would only make sure that username and password are in the params. The issue is that the :current_user is not found in conn. I don’t use anything fancy for the session and authentication management. I just set the :current_user and then check for it on each request. So for example my typical controller looks like this:

defmodule HestiaWeb.V1.ProfileController do
  use HestiaWeb, :controller
  alias Hestia.HestiaService

  action_fallback HestiaWeb.FallbackController

  def fetch(conn, params) do
    case get_session(conn, :current_user) do
      nil -> case Hestia.Session.login(params, Hestia.Repo) do
        {:ok, user} -> fetch(conn, params, user)
        _ -> {:error, :unauthorized}
      end
      user -> fetch(conn, params, user)
    end
  end

  defp fetch(conn, params, user) do
    {:ok, profile} = HestiaService.user_profile(user, Hestia.Repo)
    p = Hestia.UserProfile.json(profile, user)
    conn
    |> put_status(200)
    |> json(p)
  end

end

The issue that I have is that get_session(conn, :current_user) returns nil. So currently setting the :current_user is pointless as for every request the server needs to look up the user in the database . I’m trying to figure out what I have to do on the client side to have :current_user set on the connection for as most http request as possible.

BTW is it possible to edit my original question? I can’t find the edit button. I would update the title to

why is :current_user is always nil in conn

I think this would more clearly explain my issue and maybe lead to more responses : )

I changed the title as requested.

1 Like

My suspicion would be that the mobile libraries don’t carry cookies returned from one request to subsequent ones.

Most of the time, APIs will use a different style of authentication with opaque tokens or JWTs that are explicitly sent on every request (via an Authorization header, for instance) instead of ambient session cookies.

Side note: it’s generally preferred to put things in the session that can’t change (like a user’s ID) instead of a whole user struct (which contains fields that could change in the database).

1 Like

I’m so stupid !! I just realised why it does not work.

When copying parts of a previous project, I refactored a bit and omitted the put_session-part (as my first answer from March 30th shows).

So I wrote:

def fetch(conn, params) do
  case get_session(conn, :current_user) do
    nil -> case Hestia.Session.login(params, Hestia.Repo) do
      {:ok, user} -> fetch(conn, params, user)
      _ -> {:error, :unauthorized}
    end
    user -> fetch(conn, params, user)
  end
end

But in order to work and to have the user set in the session it should be:

def fetch(conn, params) do
  case get_session(conn, :current_user) do
    nil -> case Hestia.Session.login(params, Hestia.Repo) do
      {:ok, user} ->
        conn
        |> put_session(:current_user, user)
        |> fetch(params, user)
      _ -> {:error, :unauthorized}
    end
    user -> fetch(conn, params, user)
  end
end

:see_no_evil:

So please ignore my question.