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)
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
Instead of
Hope that helps. Let me know if the issue persists