Multitenancy with Pow

You can pass repo options to the methods used in Pow.Ecto.Context by using the :repo_opts configuration option. This makes it possible to pass on the prefix option used in multitenancy apps, so you can do the following:

config :my_app, :pow,
  # ...
  repo_opts: [prefix: "tenant_a"]

You can also pass the prefix option to Pow.Plug.Session in your WEB_PATH/endpoint.ex:

plug Pow.Plug.Session, otp_app: :my_app, repo_opts: [prefix: "tenant_a"]

And you can add it as a custom plug to use a dynamic prefix value:

defmodule MyAppWeb.Pow.TenantPlug do
  def init(config), do: config

  def call(conn, config) do
    tenant = conn.private[:tenant_prefix]
    config = Keyword.put(config, :repo_opts, [prefix: prefix])

    Pow.Plug.Session.call(conn, config)
  end
end

Triplex

With the above, it will make it very easy to set up multitenancy with Triplex.

Update your WEB_PATH/endpoint.ex using a custom plug rather than the default Pow.Plug.Session:

# lib/my_app_web/endpoint.ex
defmodule MyAppWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :my_app

  # ...

  # You should load the tenant with Triplex before calling the
  # `TriplexSessionPlug`. If you use the `ParamPlug` you could add it here:
  # plug Triplex.ParamPlug, param: :subdomain

  # ...

  plug Plug.Session, @session_options
  plug MyAppWeb.Pow.TriplexSessionPlug, otp_app: :my_app
  # ...
end

Then set up WEB_PATH/pow/triplex_session_plug.ex:

# lib/my_app_web/pow/triplex_session_plug.ex
defmodule MyAppWeb.Pow.TriplexSessionPlug do
  def init(config), do: config

  def call(conn, config) do
    tenant = conn.assigns[:current_tenant] || conn.assigns[:raw_current_tenant]
    prefix = Triplex.to_prefix(tenant)
    config = Keyword.put(config, :repo_opts, [prefix: prefix])

    Pow.Plug.Session.call(conn, config)
  end
end

Test module

# lib/my_app_web/pow/triplex_session_plug_test.exs
defmodule MyAppWeb.Pow.TriplexSessionPlugTest do
  use MyAppWeb.ConnCase

  alias MyAppWeb.Pow.TriplexSessionPlug

  @pow_config [otp_app: :my_app]
  @tenant_a "tenant_a"
  @tenant_b "tenant_b"
  @valid_user_params %{
    "email" => "test@example.com",
    "password" => "password",
    "password_confirmation" => "password"
  }

  setup do
    {:ok, conn: init_conn()}
  end

  test "handles Triplex tenants", %{conn: conn} do
    opts = TriplexSessionPlug.init(@pow_config)
    {:ok, _user, _conn} =
      conn
      |> set_triplex_tenant(@tenant_a)
      |> TriplexSessionPlug.call(opts)
      |> Pow.Plug.create_user(@valid_user_params)

    {:error, _conn} =
      conn
      |> set_triplex_tenant(@tenant_b)
      |> TriplexSessionPlug.call(opts)
      |> Pow.Plug.authenticate_user(@valid_user_params)

    {:ok, _conn} =
      conn
      |> set_triplex_tenant(@tenant_a)
      |> TriplexSessionPlug.call(opts)
      |> Pow.Plug.authenticate_user(@valid_user_params)
  end

  defp init_conn() do
    :get
    |> Plug.Test.conn("/")
    |> Plug.Test.init_test_session(%{})
    |> Phoenix.Controller.fetch_flash()
  end

  defp set_triplex_tenant(conn, tenant) do
    conn = %{conn | params: %{"subdomain" => tenant}}
    opts = Triplex.ParamPlug.init(param: :subdomain)

    Triplex.ParamPlug.call(conn, opts)
  end
end