Failling authentication

445 views Asked by At

I have a phoenix app where I want to secure all the routes except the login and user creation route. To achieve this, I use the Guardian and ComeOnIn packages. (I tried to follow this blog)
I managed to send a token to the client in the session route, and the creation of a user is working as well.
But, when I want to show all users (i.e. the index route of the user module) I get an authentication error (in my tests, I don't have the gui yet)
Here is what I have done: User controller:

defmodule WarehouseWeb.UserController do
  use WarehouseWeb, :controller

  alias Warehouse.Account
  alias Warehouse.Account.User

  action_fallback WarehouseWeb.FallbackController

  def index(conn, _params) do
    users = Account.list_users()
    render(conn, "index.json-api", data: users)
  end
# ... ...
end

Here is the router.ex

defmodule WarehouseWeb.Router do
  use WarehouseWeb, :router

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

  pipeline :api_auth do
    plug WarehouseWeb.Guardian.AuthPipeline
  end

  scope "/api", WarehouseWeb do
    pipe_through :api
    post "/register", RegistrationController, :create
    post "/token", SessionController, :create, as: :login
  end

  scope "/api", WarehouseWeb do
    pipe_through :api_auth

    resources "/users", UserController, except: [:new, :edit]
  end
end

And here are the modules necessary for Guardian:

defmodule WarehouseWeb.Guardian.AuthPipeline do
  use Guardian.Plug.Pipeline, otp_app:       :warehouse,
                              module:        WarehouseWeb.Guardian,
                              error_handler: WarehouseWeb.Guardian.AuthErrorHandler

  plug Guardian.Plug.VerifyHeader
  plug Guardian.Plug.EnsureAuthenticated
end

defmodule Warehouse.Guardian do
  use Guardian, otp: :warehouse,
                secret_key: "some secret"

  def subject_for_token(resource, _claims) do
    {:ok, to_string(resource.id)}
  end
  def resource_from_claims(claims) do
    user = Warehouse.Account.get_user!(claims["sub"])
    {:ok,  user}
  end
end

The error handler:

defmodule WarehouseWeb.Guardian.AuthErrorHandler do
  import Plug.Conn

  def auth_error(conn, {type, reason}, opts) do
    IO.inspect "authentication is not working!!"
    body = Poison.encode!(%{message: to_string(type)})
    send_resp(conn, 401, body)
  end
end

And at last my test file:

defmodule WarehouseWeb.UserControllerTest do
  use WarehouseWeb.ConnCase

  alias Warehouse.Account
  alias Warehouse.Account.User

  @create_attrs %{email: "some email", firstname: "some firstname", lastname: "some lastname", password: "some password"}
  @update_attrs %{email: "some updated email", firstname: "some updated firstname", lastname: "some updated lastname", password: "some updated password"}
  @invalid_attrs %{email: nil, firstname: nil, lastname: nil, password: nil}

  def fixture(:user) do
    {:ok, user} = Account.create_user(@create_attrs)
    user
  end

  setup %{conn: conn} do
    # create user to be logged in
    {:ok, user} = Account.create_user(%{email: "[email protected]", real_password: "abc123", real_password_confirmation: "abc123"})

    # create token for session
    {:ok, jwt, _claims} = Account.authenticate(%{user: user, password: "abc123"})

    # add authorization header to the request
    conn = conn
    |> put_req_header("authorization", "Bearer: #{jwt}")
    |> put_req_header("accept", "application/json")

    {:ok, %{conn: conn, user: user}}
  end

  describe "index" do
    test "lists all users", %{conn: conn} do
      conn = get conn, user_path(conn, :index)
      assert json_response(conn, 200)["data"] == []
    end
  end
end

Everytime I run my test I always get the error Authentication already send, which comes from my error handler.
What am I missing to make my test pass?

EDIT:
Here is the output:

1) test index lists all users (WarehouseWeb.UserControllerTest) test/warehouse_web/controllers/user_controller_test.exs:32
** (Plug.Conn.AlreadySentError) the response was already sent
code: conn = get conn, user_path(conn, :index)
stacktrace:
(plug) lib/plug/conn.ex:508: Plug.Conn.resp/3
(plug) lib/plug/conn.ex:495: Plug.Conn.send_resp/3
(guardian) lib/guardian/plug/ensure_authenticated.ex:61: Guardian.Plug.EnsureAuthenticated.respond/1
(warehouse) lib/warehouse_web/auth/auth_pipeline.ex:1: WarehouseWeb.Guardian.AuthPipeline.plug_builder_call/2
(warehouse) lib/warehouse_web/router.ex:8: WarehouseWeb.Router.api_auth/2
(warehouse) lib/warehouse_web/router.ex:1: anonymous fn/1 in WarehouseWeb.Router.match_route/4
(phoenix) lib/phoenix/router.ex:273: Phoenix.Router.call/1
(warehouse) lib/warehouse_web/endpoint.ex:1: WarehouseWeb.Endpoint.plug_builder_call/2
(warehouse) lib/warehouse_web/endpoint.ex:1: WarehouseWeb.Endpoint.call/2
(phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
test/warehouse_web/controllers/user_controller_test.exs:33: (test)

1

There are 1 answers

0
Felipe Skinner On

All your code seems pretty similar to mine, where it does work. The only difference is your test authentication... You have an extra semicolon there.

It should be

put_req_header("authorization", "Bearer #{token}")

Instead of

put_req_header("authorization", "Bearer: #{token}")

Hope that helps. Let me know if the issue persists