Update cached user credentials

You may want to update the cached user credentials when an action outside of Pow has updated the user. It's very important to understand that the cached user credentials that Pow fetches in Pow.Plug.current_user/2 is always to be considered out of date since it's a cached object.

In the following examples we'll imagine that you've added a plan column on your users table. We may want to use that plan to give them access to a certain controller actions. In this case, it's paramount that you load the user from the database.

Reload the user

defmodule MyAppWeb.ProPlanController do
  # ...

  plug :reload_user

  # ...

  defp reload_user(conn, _opts) do
    config        = Pow.Plug.fetch_config(conn)
    user          = Pow.Plug.current_user(conn, config)
    reloaded_user = MyApp.Repo.get!(MyApp.User, user.id)

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

This should always be done for any authorization actions, or any other actions that requires the actual value to be known. Do note that only the controllers that has the plug will have the reloaded user. In all other controllers, the old cached credentials will be loaded instead.

If you would like to always fetch the user as it is in the database across all your controllers, you could instead set up a module plug and add it to your endpoint:

defmodule MyAppWeb.ReloadUserPlug 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 ->
        reloaded_user = MyApp.Repo.get!(MyApp.User, user.id)

        Pow.Plug.assign_current_user(conn, reloaded_user, config)
    end
  end
end
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.ReloadUserPlug

  # ...
end

Update user in credentials cache

Let's say that you want to show the user plan on most pages. In this case we can safely rely on the cached credentials since we don't need to know the actual value in the database. The worst case is that a different plan may be shown if you haven't ensured that all plan update actions uses the below method.

We can use do_create/3 defined in the Pow.Plug.Base macro to update the cached credentials.

First we'll make a helper and import it to our controllers:

defmodule MyAppWeb.PowHelper do
  @spec sync_user(Plug.Conn.t(), map()) :: Plug.Conn.t()
  def sync_user(conn, user) do
    config = Pow.Plug.fetch_config(conn)
    plug   = Pow.Plug.get_plug(config)

    plug.do_create(conn, user, config)
  end
end
defmodule MyAppWeb do
  # ...

  def controller do
    quote do
      use Phoenix.Controller, namespace: MyAppWeb

      # ...
      import MyAppWeb.PowHelper
    end
  end

  # ...
end

Now we can call sync_user/2 in any controller actions. It could maybe be the update action for your plan controller:

defmodule MyAppWeb.PlanController do
  # ...

  def update(conn, %{"plan" => plan}) do
    conn
    |> Plug.current_user()
    |> MyApp.Users.update_plan(plan)
    |> case do
      {:ok, user} ->
        conn
        |> sync_user(user) # Update the user in the credentials cache
        |> put_flash(:info, "Plan updated successfully.")
        |> redirect(to: Routes.profile_path(conn, :show))

      {:error, %Ecto.Changeset{} = changeset} ->
        render(conn, "plan.html", changeset: changeset)
    end
  end

  # ...
end

As you can see in the above, the cached user credentials will be updated after a successful update of plan for the user. Now any subsequent pages being rendered, you'll have access to the updated plan value in the current user assign.

Another thing to note is that if you're using Pow.Plug.Session, then the session id will also be regenerated this way. This is ideal for authorization level change (what the above plan change action may be).

You may also update the plan field in a background task. In this case you won't have access to any current session, and you would have to use the Pow.Store.CredentialsCache.put/3 to update the credentials cache. However, since there are some caveats to this, it's instead recommended to find an alternative solution with the above methods.