Pow: Robust, modular, extendable user authentication and management system

Great job! However, I would recommend a plug like this to make it easier for you instead of the custom authenticate/1 method:

defmodule MyAppWeb.LoadProfilePlug do
  @doc false

  @spec init(any()) :: any()
  def init(opts), do: opts

  @doc false
  @spec call(Conn.t(), atom()) :: Conn.t()
  def call(conn, _opts) do
    config = Pow.Plug.fetch_config(conn)

    case Pow.Plug.current_user(conn, config) do
      nil ->
        conn

      user ->
        preloaded_user = MyApp.Repo.preload(user, :profile)

        Pow.Plug.assign_current_user(conn, preloaded_user, config)
    end
  end
end

And then you can call it across your whole endpoint (you could also set it on just the routes where you know will require the profile preloaded):

defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_app

  # ...

  plug Plug.Session,
    store: :cookie,
    key: "_my_app_key",
    signing_salt: "secret"

  plug Pow.Plug.Session, otp_app: :my_app

  plug MyAppWeb.LoadProfilePlug

  # ...
end

If you use a custom authenticate/1 method, then you’ll store the profile along with the user struct in the credentials cache so you have to make sure that all updates of the user struct also has the preloaded profile. This means that you should at least also add a custom update/2 method there, since if users update their profile through the pow registration controller, the cached user struct will be updated with the returning struct from update/2. By using a plug you can completely eliminate this since you’ll fetch the profile directly from the DB each time (and this also prevents that the profile can become out of date if there is some action that may update it, but doesn’t refresh the user in the credentials cache).

4 Likes