Why current_user is nil?

Hello, I have a problem with accessing current_user. Whatever I change in the plug, controller or template I finally run into the same error, that the current_user is nil. Instead of it, I’d like to show used_points from pools table for the currently logged-in user on the index page.

EDIT: I have cleaned up the code a bit, and got a new error message, so I will recreate this post with current code.

PageController:

  def index(conn, _params) do
    user_id = conn.assigns.current_user.id  
    user = Accounts.get_user!(user_id)
    |> Repo.preload([:pool])
     render(conn, "index.html",  user: user)
end

Plug

  def init(_opts), do: nil

  def call(conn, _params) do
    if conn.assigns.current_user do  #ERROR points to this line
      conn
    else
      user_id = get_session(conn, :id)

      cond do
        user = Repo.get_by(User, id: user_id) ->
          conn
          |> assign(:current_user, user)
          |> assign(:user_signed_in?, true)

        true ->
          conn
          |> assign(:current_user, nil)
          |> assign(:user_signed_in?, false)
      end
    end
  end

page/Index
<%= render EmployeeRewardAppWeb.PointsView, "show_user_points.html", conn: @conn, user: @user %>
points/show_user_points
<%= @current_user.pool.used_points %>

Router attached in the post below this one.

Currently, the error points to this line: if conn.assigns.current_user do in plug and says that key :current_user not found in: %{}

Controller actions (such as your index function) are just plugs. That means that they accept a conn struct and some parameters, and they return a conn. There isn’t usually a third parameter to the plug function, at least not for controller actions. So, the current_user as you have it defined is always nil

To get the current user, look at your plug - you are assigning the current user to the conn. That means you can access it in the downstream controllers as conn.assigns.current_user.

As for the error message, it looks like the session doesn’t have a user_id stored in it (probably before user logged it) so you probably want to check if it is nil before running the query

When I was inspecting conn earlier it was saying that current_user: nil. However, in the session I see:

current_user

    %{email: "admin@admin.com", id: 4, role_id: 2}

Where were you inspecting the conn? If you are referring to the inspect in the plug from your code sample above, then that is expected since it isn’t assigned yet. In the controller, try inspecting conn.assigns. It would also be helpful if you posted a sample of your router with the route definition and the pipe_through so we can see what is going on

If i comment out the plug and put <% IO.inspect(@conn) %> in the show_user_points.html.eex. This is the trace:

%Plug.Conn{
  adapter: {Plug.Cowboy.Conn, :...},
  assigns: %{
    layout: {EmployeeRewardAppWeb.LayoutView, "app.html"},
    users: [
      %EmployeeRewardApp.Accounts.User{
        __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
        email: "admin@admin.com",
        full_name: "Kacper Muryn",
        id: 4,
        inserted_at: ~N[2021-08-10 17:53:51],
        password: nil,
        password_confirmation: nil,
        password_digest: "$2b$12$/w4dboQ1h8tFGnb8V4NBZu74QKCMeO3a0gIJjELqFW9YHD9nDGqd6",
        pool: nil,
        role: %EmployeeRewardApp.Role{
          __meta__: #Ecto.Schema.Metadata<:loaded, "roles">,
          admin: true,
          id: 2,
          inserted_at: ~N[2021-08-10 17:53:51],
          name: "Admin",
          updated_at: ~N[2021-08-10 17:53:51],
          users: #Ecto.Association.NotLoaded<association :users is not loaded>
        },
        role_id: 2,
        updated_at: ~N[2021-08-10 17:53:51]
      },
      %EmployeeRewardApp.Accounts.User{
        __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
        email: "test2@test2.com",
        full_name: "Test 2",
        id: 7,
        inserted_at: ~N[2021-08-11 14:29:52],
        password: nil,
        password_confirmation: nil,
        password_digest: "$2b$12$y0xW5QOd28rZgc0scNL8DO8ITalqDQFROs0YcX4KKbGonNG0Z7QHC",
        pool: %EmployeeRewardApp.Points.Pool{
          __meta__: #Ecto.Schema.Metadata<:loaded, "pools">,
          id: 1,
          inserted_at: ~N[2021-08-11 14:29:52],
          starting_points: 50,
          updated_at: ~N[2021-08-11 14:29:52],
          used_points: 0,
          user: #Ecto.Association.NotLoaded<association :user is not loaded>,
          user_id: 7
        },
        role: %EmployeeRewardApp.Role{
          __meta__: #Ecto.Schema.Metadata<:loaded, "roles">,
          admin: false,
          id: 1,
          inserted_at: ~N[2021-08-10 17:53:22],
          name: "Member",
          updated_at: ~N[2021-08-10 17:53:22],
          users: #Ecto.Association.NotLoaded<association :users is not loaded>
        },
        role_id: 1,
        updated_at: ~N[2021-08-11 14:31:03]
      }

======HERE IS THE REST OF USERS THAT ARE DISPLAYED ON INDEX=====

  body_params: %{},
  cookies: %{
    "_employee_reward_app_key" => "SFMyNTY.g3QAAAACbQAAAAtfY3NyZl90b2tlbm0AAAAYZlBZcnVFcndQeTBQYXFDS0Jta0NTd2I3bQAAAAxjdXJyZW50X3VzZXJ0AAAAA2QABWVtYWlsbQAAAA9hZG1pbkBhZG1pbi5jb21kAAJpZGEEZAAHcm9sZV9pZGEC.HZJeuH0fSQwe8Pdkh7iVaMCF-iyUVIiNKFU71Vk7soc"
  },
  halted: false,
  host: "localhost",
  method: "GET",
  owner: #PID<0.5020.0>,
  params: %{},
  path_info: [],
  path_params: %{},
  port: 4000,
  private: %{
    EmployeeRewardAppWeb.Router => {[], %{}},
    :before_send => [#Function<0.5944896/1 in Plug.CSRFProtection.call/2>,
     #Function<2.64380894/1 in Phoenix.Controller.fetch_flash/2>,
     #Function<0.77458138/1 in Plug.Session.before_send/2>,
     #Function<0.23023616/1 in Plug.Telemetry.call/2>,
     #Function<1.49165416/1 in Phoenix.LiveReloader.before_send_inject_reloader/3>],
    :phoenix_action => :index,
    :phoenix_controller => EmployeeRewardAppWeb.PageController,
    :phoenix_endpoint => EmployeeRewardAppWeb.Endpoint,
    :phoenix_flash => %{},
    :phoenix_format => "html",
    :phoenix_layout => {EmployeeRewardAppWeb.LayoutView, :app},
    :phoenix_request_logger => {"request_logger", "request_logger"},
    :phoenix_router => EmployeeRewardAppWeb.Router,
    :phoenix_template => "index.html",
    :phoenix_view => EmployeeRewardAppWeb.PageView,
    :plug_session => %{
      "_csrf_token" => "fPYruErwPy0PaqCKBmkCSwb7",
      "current_user" => %{email: "admin@admin.com", id: 4, role_id: 2}
    },
    :plug_session_fetch => :done
  },
  query_params: %{},
  query_string: "",
  remote_ip: {127, 0, 0, 1},
  req_cookies: %{
    "_employee_reward_app_key" => "SFMyNTY.g3QAAAACbQAAAAtfY3NyZl90b2tlbm0AAAAYZlBZcnVFcndQeTBQYXFDS0Jta0NTd2I3bQAAAAxjdXJyZW50X3VzZXJ0AAAAA2QABWVtYWlsbQAAAA9hZG1pbkBhZG1pbi5jb21kAAJpZGEEZAAHcm9sZV9pZGEC.HZJeuH0fSQwe8Pdkh7iVaMCF-iyUVIiNKFU71Vk7soc"
  },
  req_headers: [
    {"accept",
     "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"},
    {"accept-encoding", "gzip, deflate"},
    {"accept-language", "pl,en-US;q=0.7,en;q=0.3"},
    {"cache-control", "max-age=0"},
    {"connection", "keep-alive"},
    {"cookie",
     "_employee_reward_app_key=SFMyNTY.g3QAAAACbQAAAAtfY3NyZl90b2tlbm0AAAAYZlBZcnVFcndQeTBQYXFDS0Jta0NTd2I3bQAAAAxjdXJyZW50X3VzZXJ0AAAAA2QABWVtYWlsbQAAAA9hZG1pbkBhZG1pbi5jb21kAAJpZGEEZAAHcm9sZV9pZGEC.HZJeuH0fSQwe8Pdkh7iVaMCF-iyUVIiNKFU71Vk7soc"},
    {"host", "localhost:4000"},
    {"referer", "http://localhost:4000/"},
    {"sec-fetch-dest", "document"},
    {"sec-fetch-mode", "navigate"},
    {"sec-fetch-site", "same-origin"},
    {"sec-fetch-user", "?1"},
    {"upgrade-insecure-requests", "1"},
    {"user-agent",
     "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:91.0) Gecko/20100101 Firefox/91.0"}
  ],
  request_path: "/",
  resp_body: nil,
  resp_cookies: %{},
  resp_headers: [
    {"cache-control", "max-age=0, private, must-revalidate"},
    {"x-request-id", "Fp0Px8xekp4XR-gAAD7i"},
    {"x-frame-options", "SAMEORIGIN"},
    {"x-xss-protection", "1; mode=block"},
    {"x-content-type-options", "nosniff"},
    {"x-download-options", "noopen"},
    {"x-permitted-cross-domain-policies", "none"},
    {"cross-origin-window-policy", "deny"}
  ],
  scheme: :http,
  script_name: [],
  secret_key_base: :...,
  state: :unset,
  status: nil
}
[info] Sent 200 in 16ms

Here is my router:

defmodule EmployeeRewardAppWeb.Router do
  use EmployeeRewardAppWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    # plug EmployeeRewardAppWeb.Plugs.LoadUser

  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", EmployeeRewardAppWeb do
    pipe_through :browser

    get "/", PageController, :index
    resources "/sessions", SessionController, only: [:new, :create, :delete]
  end

  scope "/", EmployeeRewardAppWeb do
    pipe_through [:browser, EmployeeRewardAppWeb.CheckAdminPlug]
    resources "/users", UserController
  end

  # Other scopes may use custom stacks.
  # scope "/api", EmployeeRewardAppWeb do
  #   pipe_through :api
  # end

  # Enables LiveDashboard only for development
  #
  # If you want to use the LiveDashboard in production, you should put
  # it behind authentication and allow only admins to access it.
  # If your application does not have an admins-only section yet,
  # you can use Plug.BasicAuth to set up some basic authentication
  # as long as you are also using SSL (which you should anyway).
  if Mix.env() in [:dev, :test] do
    import Phoenix.LiveDashboard.Router

    scope "/" do
      pipe_through :browser
      live_dashboard "/dashboard", metrics: EmployeeRewardAppWeb.Telemetry
    end
  end
end

I don’t understand why in the other plug it is possible to get current_user. In my router, as you can see, I have this plug:

  scope "/", EmployeeRewardAppWeb do
    pipe_through [:browser, EmployeeRewardAppWeb.Plugs.CheckAdminPlug]
    resources "/users", UserController
  end

In the plug:

  def init(opts) do
    opts
  end

  def call(conn, _opts) do
    user = get_session(conn, :current_user)
    if user && EmployeeRewardAppWeb.Helpers.RoleChecker.is_admin?(user) do
      conn
    else
      conn
      |> send_resp(:unauthorized, "You don't have permission to visit this page.")
      |> halt()
    end
    conn
  end

I can’t access /user when logged in user is not admin.
Also, I have this in layout view.
In the layout view:

  def current_user(conn) do
    Plug.Conn.get_session(conn, :current_user)
  end

And it works in the app.html.eex - I see Log in for not logged in user and when I’m logged in I see my email and Log out.


                <%= if user = current_user(@conn) do %>
                    Logged in as
                    <strong><%= user.email %></strong>
                  </a>
              </li>
              <li class="nav-item">
                <%= link "Log out", to: Routes.session_path(@conn, :delete, user.id), method: :delete, class: "nav-link" %>
                <% else %>
                  <%= link "Log in", to: Routes.session_path(@conn, :new), class: "nav-link" %>
                <% end %>

Why it works in one plug and in the other I can’t get current_user? I’ve been trying different strategies (such as overriding action) but i still run into the same error that the current_user is nil.