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.
The HTTP protocol is stateless. This means that
redirect_toreturns 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
redirectis 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_tokenis automatically called insidesign_in, so you the pipelines should include plugs to work with sessions.