2

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.

Afshin Moazami
  • 2,092
  • 5
  • 33
  • 55
michjo
  • 407
  • 2
  • 17

1 Answers1

0

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.

Virviil
  • 622
  • 5
  • 14
  • I edited my original post to show my auth pipeline and updated my `router.ex` to show which routes are now protected and which are not. I have tried including the plug for verifying the session in my pipeline, however if I do so I will get an `invalid token` message immediately after signing in and redirecting to the users index. – michjo Jun 30 '19 at 15:54
  • I still don't see the pipelines in your router file. Maybe the problem is because you deleted sessions plug from browser pipeline – Virviil Jul 01 '19 at 06:53
  • The protected routes are being piped through the authline: `pipe_through [:browser, :auth]`, do you mean something additional is required? – michjo Jul 01 '19 at 12:25
  • I misunderstood your comment, I have expanded the code to show the `:browser` pipeline. It is not modified from the default after generating a new phoenix app, and the `:fetch_session` plug was not removed. – michjo Jul 01 '19 at 13:57
  • You are using `Guardian.Plug.sign_in(user)`. But the documentation tells us use `MyApp.Guardian.Plug.sign_in(user)` Do you have this module in your code? – Virviil Jul 01 '19 at 14:06
  • I have my `Guardian` module aliased. `Guardian.Plug.sign_in(user)` is pointing to the `MyApp.Guardian` module, which in turn is using the Guardian package (`use Guardian, otp_app: :phoenix_app`) – michjo Jul 01 '19 at 14:15
  • Can you check, that after your login, conn has the user’s token in season? Via `get_session`? And that the session in the next request is the same on the next request? – Virviil Jul 01 '19 at 14:18
  • After calling `login` from my `auth.ex`, I don't have a user token in the session. I only have `guardian_default_token` and `_csrf_token`, even though this method invokes the `Guardian.Plug.sign_in(user)` which I thought should be creating the user session.. – michjo Jul 01 '19 at 14:35
  • Yep, here is the problem. You can manually do the same checks that sign_in do, and understand why it’s not writing in the session – Virviil Jul 01 '19 at 14:46