User authentication within umbrella project

I am building my first umbrella project and I am trying to figure out the best way to implement authentication. I have only built a few other Phoenix apps and whenever I needed user authentication I rolled my own that was session based.

Now there are a couple of nuances about Umbrella projects that I am unsure of. I was looking at Pow but would I need to change the adapter to a JWT based auth using Guardian?

Right now my app consists of three applications: a db, user_web_interface, & logic_app. I tried setting up Pow through the docs, but wasn’t able to. Another reason I was looking at JWT is the user_interface is just a web interface for a native mobile app I am eventually going to plug in and use within this application.

:wave:

What stops you from using the same approach as before, that is, making the authentication session based? That’s what I use both for websites and ios apps when working with monoliths (elixir umbrellas) on the backend, and it works quite nicely.

2 Likes

It’s possible with Pow to change the plug auth to use Guardian: https://github.com/danschultzer/pow#authorization-plug

defmodule MyAppWeb.Pow.Plug do
  use Pow.Plug.Base

  def fetch(conn, config) do
    user = MyApp.Guardian.Plug.current_resource(conn)

    {conn, user}
  end

  def create(conn, user, config) do
    conn = MyApp.Guardian.Plug.sign_in(conn, user)

    {conn, user}
  end

  def delete(conn, config) do
    MyApp.Guardian.Plug.signout(conn)
  end
end

defmodule MyAppWeb.Endpoint do
  # ...

  plug MyAppWeb.Pow.Plug, otp_app: :my_app
end

Couldn’t you get this to work?

1 Like

I have built one iOS app and that was several years ago. I just don’t have experience with mobile development just yet and had heard that it would require tokens. This was the sole reason I was wondering about JWTs, but that’s good to know.

The only thing that I have tried so far is implementing Pow within my umbrella application. I followed the readme for handling umbrella applications and I got a HtmlProtocol error, and I have been trying session based the entire time.

In my personal experience, the real JWT tokens are required when you are working with microservices on the backend. Since I usually don’t, basic bearer tokens that are passed in a header to the backend with each http request are enough in almost all of my use cases. The values for these tokens are the same as what Phoenix.Token uses for cookies.

1 Like

I would be happy to help out if you still want to get Pow running. The feedback helps me catch most edge cases in the documentation :slight_smile: I tested out with a brand new umbrella app to see if anything was missing, but it worked just fine, so not sure what triggers the protocol error.

If you want to keep going with Pow, could you post the actual error you got (maybe open an issue on https://github.com/danschultzer/pow/issues)?

2 Likes

The actual error I am getting is:

[error] #PID<0.427.0> running UserPwaWeb.Endpoint (connection #PID<0.426.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /registration/new
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Phoenix.HTML.FormData not implemented for #Ecto.Changeset<action: nil, changes: %{}, errors: [password_hash: {"can't be blank", [validation: :required]}, password: {"can't be blank", [validation: :required]}, email: {"can't be blank", [validation: :required]}], data: #Db.Users.User<>, valid?: false>. This protocol is implemented for: Plug.Conn
...

The problem could just be my lack of understanding with umbrella projects.

To reproduce the error I first created a new umbrella project. I created apps called UserPwa & Db within the apps folder and added pow to the mix.exs for both of them. I add {:db, in_umbrella: true} to the UserPwa mix.exs file. After running mix deps.get I cd into my Db app and run mix pow.ecto.install.

After that I cd into my interface app UserPwa and add:

config :user_pwa, :pow,
  user: Db.Users.User
  repo: Db.Repo

to my config/config.ex. I then add plug Pow.Plug.Session, otp_app: :user_pwa to my user_pwa_web/endpoint.ex file, making sure to include it directly after the Plug.Session plug.

After that I added to my router:

use Pow.Phoenix.Router
..

pipeline :protected do
  plug Pow.Plug.RequireAuthenticated,
    error_handler: Pow.Phoenix.PlugErrorHandler
end

scope "/" do # Note the lack of UserPwaWeb (I'm assuming this is on purpose because of how the action is sent to the RegistrationController?)
  pipe_through :browser

    pow_routes()
  end

The only thing that I had to do extra that wasn’t in the docs (that I immediately saw) was that I had to add the mix ecto.setup alias to my mix file.

I added a new aliases function to my Db app’s config:

defp aliases do
  [
    "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
    "ecto.reset": ["ecto.drop", "ecto.setup"],
    test: ["ecto.create --quiet", "ecto.migrate", "test"]
  ]
end

After that I run mix ecto.setup it warns me that the seeds script failed because there really isn’t a seeds file created yet, and then I start up the server.

The full error is:

[error] #PID<0.438.0> running UserPwaWeb.Endpoint (connection #PID<0.429.0>, stream id 4) terminated
Server: localhost:4000 (http)
Request: GET /registration/new
** (exit) an exception was raised:
    ** (Protocol.UndefinedError) protocol Phoenix.HTML.FormData not implemented for #Ecto.Changeset<action: nil, changes: %{}, errors: [password_hash: {"can't be blank", [validation: :required]}, password: {"can't be blank", [validation: :required]}, email: {"can't be blank", [validation: :required]}], data: #Db.Users.User<>, valid?: false>. This protocol is implemented for: Plug.Conn
        (phoenix_html) deps/phoenix_html/lib/phoenix_html/form_data.ex:1: Phoenix.HTML.FormData.impl_for!/1
        (phoenix_html) deps/phoenix_html/lib/phoenix_html/form_data.ex:15: Phoenix.HTML.FormData.to_form/2
        (phoenix_html) lib/phoenix_html/form.ex:287: Phoenix.HTML.Form.form_for/4
        (pow) lib/pow/phoenix/templates/registration_template.ex:3: Pow.Phoenix.RegistrationTemplate.render/2
        (user_pwa) lib/user_pwa_web/templates/layout/app.html.eex:26: UserPwaWeb.LayoutView."app.html"/1
        (phoenix) lib/phoenix/view.ex:399: Phoenix.View.render_to_iodata/3
        (phoenix) lib/phoenix/controller.ex:729: Phoenix.Controller.__put_render__/5
        (phoenix) lib/phoenix/controller.ex:746: Phoenix.Controller.instrument_render_and_send/4
        (pow) lib/pow/phoenix/controllers/registration_controller.ex:1: Pow.Phoenix.RegistrationController.action/2
        (pow) lib/pow/phoenix/controllers/registration_controller.ex:1: Pow.Phoenix.RegistrationController.phoenix_controller_pipeline/2
        (user_pwa) lib/user_pwa_web/endpoint.ex:1: UserPwaWeb.Endpoint.instrument/4
        (phoenix) lib/phoenix/router.ex:275: Phoenix.Router.__call__/1
        (user_pwa) lib/user_pwa_web/endpoint.ex:1: UserPwaWeb.Endpoint.plug_builder_call/2
        (user_pwa) lib/plug/debugger.ex:122: UserPwaWeb.Endpoint."call (overridable 3)"/2
        (user_pwa) lib/user_pwa_web/endpoint.ex:1: UserPwaWeb.Endpoint.call/2
        (phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:34: Phoenix.Endpoint.Cowboy2Handler.init/2
        (cowboy) /Users/joemarion/CharlottesWebAgency/jixler/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
        (cowboy) /Users/joemarion/CharlottesWebAgency/jixler/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
        (cowboy) /Users/joemarion/CharlottesWebAgency/jixler/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
        (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3

Sounds like you don’t have :phoenix_ecto installed in your phoenix app?

This is how the mix.exs looks like in my phoenix app:

  defp deps do
    [
      {:phoenix, "~> 1.4.0"},
      {:phoenix_pubsub, "~> 1.1"},
      {:phoenix_ecto, "~> 4.0"},
      {:phoenix_html, "~> 2.11"},
      {:phoenix_live_reload, "~> 1.2", only: :dev},
      {:gettext, "~> 0.11"},
      {:pow_demo_umbrella, in_umbrella: true},
      {:jason, "~> 1.0"},
      {:plug_cowboy, "~> 2.0"},

      {:pow, "~> 1.0.0"}
    ]
  end
4 Likes

That was the problem! I had forgotten about phoenix_ecto and have never seen that error before. Thanks for the help and I am looking forward to using pow!

1 Like