(Plug.Conn.AlreadySentError) the response was already sent

Using Guardian for register/login.
I am getting this error when I try to log in:

[info] Sent 401 in 819ms
[error] #PID<0.440.0> running WebPageWeb.Endpoint (cowboy_protocol) terminated
Server: localhost:4000 (http)
Request: POST /users/login_auth
** (exit) an exception was raised:
    ** (Plug.Conn.AlreadySentError) the response was already sent
        (plug) lib/plug/conn.ex:668: Plug.Conn.put_resp_header/3
        (phoenix) lib/phoenix/controller.ex:402: Phoenix.Controller.redirect/2
        (web_page) lib/web_page_web/controllers/user_controller.ex:1: WebPageWeb.UserController.action/2
        (web_page) lib/web_page_web/controllers/user_controller.ex:1: WebPageWeb.UserController.phoenix_controller_pipeline/2
        (web_page) lib/web_page_web/endpoint.ex:1: WebPageWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:278: Phoenix.Router.__call__/1
        (web_page) lib/web_page_web/endpoint.ex:1: WebPageWeb.Endpoint.plug_builder_call/2
        (web_page) lib/plug/debugger.ex:122: WebPageWeb.Endpoint."call (overridable 3)"/2
        (web_page) lib/web_page_web/endpoint.ex:1: WebPageWeb.Endpoint.call/2
        (plug) lib/plug/adapters/cowboy/handler.ex:16: Plug.Adapters.Cowboy.Handler.upgrade/4
        (cowboy) src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

Controller:

def login(conn, _params) do
    changeset = Users.change_user(%User{})
    render(conn, "login.html", changeset: changeset)
  end

  def login_auth(conn, %{"user" => %{"email" => email, "password" => password}}) do
    Auth.authenticate_user(email, password)
    |> login_reply(conn)
  end

  defp login_reply({:error, error}, conn) do
    conn
    |> put_flash(:error, error)
    |> redirect(to: "/")
  end

  defp login_reply({:ok, user}, conn) do
    conn
    |> put_flash(:success, "Welcome back!")
    |> Guardian.Plug.sign_in(user)
    |> redirect(to: "/")
  end

Auth.ex

def authenticate_user(email, plain_text_password) do
    query = from u in User, where: u.email == ^email
    Repo.one(query)
    |> check_password(plain_text_password)
  end
 
  defp check_password(nil, _), do: {:error, "Incorrect email or password"}
 
  defp check_password(user, plain_text_password) do
    case Bcrypt.checkpw(plain_text_password, user.password_hash) do
      true -> {:ok, user}
      false -> {:error, "Incorrect email or password"}
    end
  end

Router:

defmodule WebPageWeb.Router do
  use WebPageWeb, :router

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

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

  pipeline :auth do
    plug WebPage.Pipeline
  end

  pipeline :ensure_auth do
    plug Guardian.Plug.EnsureAuthenticated
  end

  scope "/", WebPageWeb do
    pipe_through [:browser, :auth] # Use the default browser stack

    get "/", PageController, :index
    post("/login_auth", UserController, :login_auth)
    get("/login", UserController, :login)
    get("/logout", UserController, :logout)
    resources "/users", UserController

  end

  scope "/admin", WebPageWeb do
    pipe_through [:browser, :auth, :ensure_auth]
    post("/login_auth", UserController, :login_auth)
    get "/", PageController, :index
    get("/login", UserController, :login)
    get("/logout", UserController, :logout)
    resources "/users", UserController

  end

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

So I investigated the problem and it comes out the verifications are allright.
Parameters which I give are correct but I couldn’t figure out where the the problem comes from.

After the login it saves the user into the connection regardless the given error.

1 Like

You can check out this issue( Using halt on auth_error #401)

OR
you can provide more details like apps guardian module ie. WebpageWeb.Guardian and WebPage.Pipeline and Error handler for Guardian module.

1 Like

Guardian.ex

defmodule WebPage.Guardian do
    use Guardian, otp_app: :watch_tok_phoenix
    alias WebPage.Users

    def subject_for_token(user, _claims) do
      {:ok, to_string(user.id)}
    end
    def resource_from_claims(claims) do
      user = claims["sub"]
      |> Users.get_user!
      {:ok, user}
      # If something goes wrong here returns {:error, reason}
    end
  end

Pipeline.ex

defmodule WebPage.Pipeline do
    use Guardian.Plug.Pipeline,
      otp_app: :web_page,
      error_handler: WebPage.ErrorHandler,
      module: WebPage.Guardian
    # If there is a session token, validate it
    plug Guardian.Plug.VerifySession, claims: %{"typ" => "access"}
    # If there is an authorization header, validate it
    plug Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"}
    # Load the user if either of the verifications worked
    plug Guardian.Plug.LoadResource, allow_blank: true
  end

error_handler.ex

defmodule WebPage.ErrorHandler do
    import Plug.Conn
    def auth_error(conn, {type, _reason}, _opts) do
      body = to_string(type)
      conn
      |> put_resp_content_type("text/plain")
      |> send_resp(401, body)
    end
  end

have you tried piping halt() in auth_error function in WebPage.ErrorHandler.
error_handler.ex

defmodule WebPage.ErrorHandler do
    import Plug.Conn
    def auth_error(conn, {type, _reason}, _opts) do
      body = to_string(type)
      conn
      |> put_resp_content_type("text/plain")
      |> send_resp(401, body)
      |> halt()
    end
  end

It looks like an auth error(401) in response before sending ** (Plug.Conn.AlreadySentError).

This lets it pass through auth_error function in error_handler.ex where response is sent i.e,

|> send_resp(401, body)

Since there is no halt, connection is passed forward and you are trying to respond through redirecting at “/”, which is throwing error because its already sent using send_resp() in auth_error function.

1 Like

It is not helping.

You can troubleshoot from start like inspect Plug.Conn’s session since JWT is stored in session from Guardian.Plug.sign_in , check if token is available after login i.e, Authorisation token.

You can try WebPage.Guardian.Plug instead of Guardian.Plug inside login_reply function

defp login_reply({:ok, user}, conn) do
    conn
    |> put_flash(:success, "Welcome back!")
    |> WebPage.Guardian.Plug.sign_in(user)
    |> redirect(to: "/")
  end

OR

Can I check the source code, if you don’t have any problem ? you can message me.

Hello,

use Guardian, otp_app: :watch_tok_phoenix

replace with

use Guardian, otp_app: :web_page

Thanks for your response, but I actually managed to resolve the problem. It was actually a problem in the library. We had to wait for the latest updates.

How did you solve it? What was the root cause of the problem? I am facing the same problem right now.

I have just resolved my problem. In my case, my configuration in config.exs was wrong.

It was like this before:

config :guardian, Hello.Users.Guardian,
  issuer: "hello",
  secret_key: "4T5tuL+XbJ5yrmFKP/QfpE8gaqIRaTnmeXuIfuZGKkKKzc0dgGYhdVsWTrJWEVJ2"

I have changed it into:

config :hello, Hello.Users.Guardian,
  issuer: "hello",
  secret_key: "4T5tuL+XbJ5yrmFKP/QfpE8gaqIRaTnmeXuIfuZGKkKKzc0dgGYhdVsWTrJWEVJ2"
4 Likes

This saves my life!

3 Likes