User does not persist after begin logged in

340 Views Asked by At

I am building my first Elixir app using Guardian and I am having an issue where a User can log in and be authenticated, but upon redirect to the next page the conn no longer stores the user information and Guardian.Plug.is_authenticated? returns false.

session_controller.ex

  ....
  def create(conn, %{"session" => %{"email" => email, "password" => password}}) do
    case PhoenixApp.Auth.authenticate_user(email, password) do
      {:ok, user} ->
        conn
        |> PhoenixApp.Auth.login(user)
        |> put_flash(:info, "Welcome back!")
        |> redirect(to: "/users")

      {:error, _reason} ->
        conn
        |> put_flash(:error, "Invalid username or password.")
        |> render("new.html")
    end
  end
  ...

router.ex

  ...
  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  scope "/", PhoenixAppWeb do
    pipe_through [:browser]

    get "/signup", UserController, :new
    get "/login", SessionController, :new
    post "/login", SessionController, :create
    delete "/logout/:id", SessionController, :delete
  end

  scope "/", PhoenixAppWeb do
    # Protected routes
    pipe_through [:browser, :auth]

    resources "/users", UserController, except: [:new]
    get "/", PageController, :index
  end

  # Auth pipeline
  pipeline :auth do
    plug(PhoenixApp.Auth.AuthAccessPipeline)
  end
  ...

auth.ex

  ...
  def login(conn, user) do
   conn
    |> Guardian.Plug.sign_in(user)
    |> assign(:current_user, user)
    |> IO.inspect
    |> put_user_token(user)
  end
  ...

auth_access_pipeline.ex

defmodule PhoenixApp.Auth.AuthAccessPipeline do
  @moduledoc false

  use Guardian.Plug.Pipeline,
    otp_app: :phoenix_app,
    error_handler: PhoenixApp.Auth.AuthErrorHandler

  plug(Guardian.Plug.Pipeline,
    module: PhoenixApp.Guardian,
    error_handler: PhoenixApp.Auth.AuthErrorHandler
  )

  plug(Guardian.Plug.VerifySession, claims: %{"typ" => "access"})
  # plug(Guardian.Plug.EnsureAuthenticated)
  # plug(Guardian.Plug.LoadResource)
end

The IO.inspect(conn) from my login method returns a JSONified User struct for the user that just signed in in the assigns key under current_user, and also stores a user_token with a token. If you inspect the conn after redirect to /users, the current_user in assigns is nil and there is no user_token.

1

There are 1 best solutions below

9
Virviil On

The HTTP protocol is stateless. This means that redirect_to returns nothing then the instruction to the browser how to load another(next) page.

conn in the auth request disappears with all the stored user resources and tokens after the redirect is called.

Now browser initiates new connection to the server, which creates absolutely new conn, which knows nothing about previous (and can be for example handled by another server if one has a cluster).

So, how to "store state" (in our case - user resource) between connections?

With cookies of course. One should put user token inside cookie in one request, and retrieve the token from the cookie in all the next requests.

Fairly saying, Guardian has two different ways to store and retrieve token in/from cookie: directly with remember_me/VerifyCookie Plug or with sessions: put_session_token/VerifySession Plug

put_session_token is automatically called inside sign_in, so you the pipelines should include plugs to work with sessions.