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)