# Getting Started This guide walks you through adding multi-account linking and switching to an existing Ash + AshAuthentication + Phoenix app. By the end, your users will be able to link multiple accounts to a single browser session and switch between them without re-authenticating. ## Prerequisites AshMultiAccount adds multi-account support **on top of** an existing Ash + AshAuthentication + Phoenix app. Before installing, you need: ### Required These are pulled in automatically as transitive dependencies of `ash_multi_account`: - **Ash and Spark** - **AshAuthentication** — you must have at least one authentication strategy configured - **Phoenix** — controllers, plugs, and router all require it You also need: - **A user resource** — an Ash resource with AshAuthentication set up and registered in a domain (can be any module name, e.g. `MyApp.Accounts.User`, `MyApp.Accounts.Person`, etc.) - **AshAuthentication Phoenix** (`ash_authentication_phoenix`) — provides `AshAuthentication.Phoenix.Controller` which is used by the auth controller that handles sign-in callbacks. Most Phoenix + AshAuthentication apps already have this. > **Don't have a user resource yet?** Follow the [AshAuthentication Getting Started guide](https://hexdocs.pm/ash_authentication/get-started.html) to create one with an authentication strategy, then come back here. ### Also needed for LiveView If your app uses LiveView for authenticated pages, also add: - **Phoenix LiveView** (`phoenix_live_view`) — needed for the LiveView hook (`AshMultiAccount.Phoenix.LiveHook`) and the account switcher component (`AshMultiAccount.Phoenix.Components`). > **Note:** `ash_authentication_phoenix` also provides `AshAuthentication.Phoenix.LiveSession` which the multi-account LiveView hook runs alongside. You likely already have it if your app uses AshAuthentication with LiveView. The installer patches your existing resources and router; it does not create a User resource, domain, or authentication setup from scratch. Specific version requirements are defined in the library's `mix.exs` — running `mix deps.get` will ensure compatible versions are resolved automatically. ### Data Layer AshMultiAccount is **data layer agnostic**. It works with any Ash data layer — AshPostgres, AshSqlite, ETS, or others. The library generates standard Ash resources with attributes, relationships, and actions that work on any data layer. Both the demo app and the library's own test suite use ETS. ### Authentication Strategy AshMultiAccount is **strategy-agnostic**. It works with any AshAuthentication strategy — password, OAuth2, magic links, API keys, or any combination. The library hooks into the session layer *after* authentication completes, so it doesn't care how the user originally signed in. The demo app uses password authentication for simplicity, but the same setup works with any strategy. ## Installation Add the dependencies to your `mix.exs`. What you need depends on your setup: ### Using Igniter (recommended) ```elixir # mix.exs def deps do [ {:ash_multi_account, "~> 0.1.0"}, {:igniter, "~> 0.6"}, # If not already present: {:ash_authentication_phoenix, "~> 2.0"}, # Also add if using LiveView: {:phoenix_live_view, "~> 1.0"} ] end ``` ### Manual ```elixir # mix.exs def deps do [ {:ash_multi_account, "~> 0.1.0"}, # If not already present: {:ash_authentication_phoenix, "~> 2.0"}, # Also add if using LiveView: {:phoenix_live_view, "~> 1.0"} ] end ``` Run `mix deps.get` to fetch the dependencies. ### Using Igniter (recommended) The Igniter installer automates steps 1–6 below: 1. Adds the `AshMultiAccount` extension to your User resource 2. Creates the LinkedAccount resource (or patches it if it exists) 3. Registers LinkedAccount in your domain 4. Patches your auth controller's `success/4` to call `put_user_id/3` 5. Creates a `MultiAccountController` 6. Adds `use AshMultiAccount.Phoenix.Router`, the `Plug`, and routes to your router Steps 7–8 (LiveView hook / controller plug and account switcher component) are app-specific — the installer prints post-install instructions for these. ```sh mix igniter.install ash_multi_account ``` If `ash_multi_account` is a path or git dependency (not yet on Hex), run the installer directly instead: ```sh mix ash_multi_account.install ``` You can also specify resource module names explicitly: ```sh mix igniter.install ash_multi_account \ --user MyApp.Accounts.User \ --linked-account MyApp.Accounts.LinkedAccount ``` After running the installer: - **LiveView apps:** Follow the post-install instructions for the LiveView hook (Step 7) and component (Step 8) - **Controller-only apps:** Add the `LoadMultiAccount` plug (Step 7 alt) and component (Step 8) If using a database-backed data layer (AshPostgres, AshSqlite), update the generated LinkedAccount resource's data layer configuration and run migrations: ```bash mix ash.codegen create_linked_accounts mix ash.migrate ``` ### Manual Follow all steps below to set up AshMultiAccount manually. ## Step 1: Add the Extension to Your User Resource Add `AshMultiAccount` to your User resource's extensions and configure the `multi_account` section: ```elixir defmodule MyApp.Accounts.User do use Ash.Resource, domain: MyApp.Accounts, data_layer: ..., # any Ash data layer (AshPostgres, AshSqlite, ETS, etc.) extensions: [AshAuthentication, AshMultiAccount] multi_account do linked_account_resource MyApp.Accounts.LinkedAccount active_check {:status, :active} display_fields [:name, :email, :avatar_url] max_linked_accounts 5 end # ... your existing attributes, actions, etc. end ``` ### Configuration Options | Option | Required | Default | Description | |--------|----------|---------|-------------| | `linked_account_resource` | Yes | — | The LinkedAccount resource module | | `active_check` | No | `nil` | `{field, value}` tuple — only active users can be linked/switched to | | `display_fields` | No | `[]` | Fields loaded on users for the switcher UI | | `max_linked_accounts` | No | `5` | Maximum linked accounts per session | The extension's transformer will automatically add: - A `:linked_accounts` calculation that resolves linked account records for a session - A `:get_user_with_linked_accounts` read action used by the LiveView hook ## Step 2: Create the LinkedAccount Resource Create a new resource with the `AshMultiAccount.LinkedAccount` extension: ```elixir defmodule MyApp.Accounts.LinkedAccount do use Ash.Resource, domain: MyApp.Accounts, data_layer: ..., # any Ash data layer (AshPostgres, AshSqlite, ETS, etc.) extensions: [AshMultiAccount.LinkedAccount] multi_account do user_resource MyApp.Accounts.User end # If using a database-backed data layer, add its config here. # For example, with AshPostgres: # postgres do # table "linked_accounts" # repo MyApp.Repo # end end ``` The transformer generates the full schema automatically: - **Attributes**: `session_token`, `status` (`:active`/`:inactive`), timestamps - **Relationships**: `primary_user` and `linked_user` (both `belongs_to` your User) - **Actions**: `create_linked_account`, `get_linked_accounts`, `activate`, `deactivate`, `read`, `destroy` - **Calculations**: `is_active?` - **Identity**: unique constraint on `{primary_user_id, linked_user_id, session_token}` If using a database-backed data layer, generate and run the migration: ```bash mix ash.codegen create_linked_accounts mix ash.migrate ``` > **Note:** In-memory data layers like ETS require no migration step. ## Step 3: Register in Your Domain Add the LinkedAccount resource to your domain: ```elixir defmodule MyApp.Accounts do use Ash.Domain resources do resource MyApp.Accounts.User resource MyApp.Accounts.LinkedAccount end end ``` ## Step 4: Update the Auth Controller Your AshAuthentication auth controller needs to write the user ID to the session in a format the multi-account hook can read. Add a call to `AshMultiAccount.Phoenix.Session.put_user_id/3` in your success callback: ```elixir defmodule MyAppWeb.AuthController do use MyAppWeb, :controller use AshAuthentication.Phoenix.Controller def success(conn, _activity, user, _token) do conn |> store_in_session(user) |> AshMultiAccount.Phoenix.Session.put_user_id(user.id) |> assign(:current_user, user) |> redirect(to: ~p"/") end def failure(conn, _activity, _reason) do conn |> put_flash(:error, "Incorrect email or password") |> redirect(to: ~p"/sign-in") end def sign_out(conn, _params) do conn |> clear_session(:YOUR_OTP_APP) |> redirect(to: ~p"/sign-in") end end ``` > **Note:** Replace `:YOUR_OTP_APP` in `clear_session/1` with your OTP application name (the `:app` value in your `mix.exs` project config). > **Why is `put_user_id` needed?** AshAuthentication stores a JWT subject string in the session. The multi-account hook needs a plain user ID to resolve the current user after account switches. `put_user_id/3` writes the subject in a format both systems can read. ## Step 5: Create the Multi-Account Controller ```elixir defmodule MyAppWeb.MultiAccountController do use MyAppWeb, :controller use AshMultiAccount.Phoenix.Controller, user_resource: MyApp.Accounts.User # Optionally override redirect paths: # def after_link_path(_conn), do: ~p"/" # def after_switch_path(_conn), do: ~p"/" # def sign_in_path(_conn, primary_user_id), do: ~p"/sign-in?return_to=/link/p/#{primary_user_id}" end ``` The controller mixin provides two actions: - `link_account/2` — links a newly signed-in user to an existing primary account - `switch_to_account/2` — switches the session to a different linked user ## Step 6: Add Routes ```elixir defmodule MyAppWeb.Router do use MyAppWeb, :router use AshMultiAccount.Phoenix.Router pipeline :browser do # ... existing plugs ... plug AshMultiAccount.Phoenix.Plug end scope "/", MyAppWeb do pipe_through :browser # Generates: # GET /link/p/:primary_user_id -> MultiAccountController.link_account # POST /link/p/:primary_user_id -> MultiAccountController.link_account # GET /link/switch_to/:user_id -> MultiAccountController.switch_to_account multi_account_routes MultiAccountController, MyApp.Accounts.User end end ``` `AshMultiAccount.Phoenix.Plug` ensures a session token UUID exists before any multi-account routes are hit. ## Step 7: LiveView Setup > **Skip this step** if your app doesn't use LiveView. See [Step 7 alt](#step-7-alt-controller-only-setup) instead. Add the multi-account hook to your authenticated live sessions. It should run **after** AshAuthentication's hook: ```elixir live_session :authenticated, on_mount: [ {AshAuthentication.Phoenix.LiveSession, :load_from_session}, {AshMultiAccount.Phoenix.LiveHook, {:load_multi_account, MyApp.Accounts.User}} ] do live "/", HomeLive # ... more live routes end ``` The hook sets two assigns on every mount: - `@current_user` — the user currently acting (may differ from primary after a switch) - `@primary_user` — the primary account owner (`nil` when not in multi-account mode) > **Tip:** If your app has both LiveView and controller-rendered pages, you can add the `LoadMultiAccount` plug (Step 7 alt) alongside the LiveView hook. They read the same session keys and coexist without conflict. ## Step 7 alt: Controller-Only Setup > **Skip this step** if you already added the LiveView hook above and don't have controller-rendered pages that need multi-account assigns. Add the `LoadMultiAccount` plug to your browser pipeline: ```elixir pipeline :browser do # ... existing plugs ... plug AshMultiAccount.Phoenix.Plug plug AshMultiAccount.Phoenix.LoadMultiAccount, user_resource: MyApp.Accounts.User end ``` This plug sets the same `@current_user` and `@primary_user` assigns on `conn` that the LiveView hook sets on the socket. It must run after `:fetch_session` and `AshMultiAccount.Phoenix.Plug`. For controller-only apps, this is the only integration step needed — the plug handles user resolution and multi-account switching for all controller-rendered pages. ## Step 8: Add the Account Switcher Component Use the slot-based component in your layout or navigation: ```heex <:account :let={account}> <.link :if={!account.current?} href={account.switch_url}> {account.user.name} {account.user.name} (current) <:add_account :let={url}> <.link href={url}>Add another account ``` The component imposes no styling — you control all HTML and CSS through slots. It works identically in both LiveView templates and controller-rendered templates — it's a standard `Phoenix.Component` that only needs `@current_user` and `@primary_user` assigns. ## What's Next? - [How It Works](../topics/how-it-works.md) — understand the data model, session tokens, and linking/switching flows - [Phoenix Integration](../topics/phoenix-integration.md) — deep dive into each Phoenix module - [Testing](../topics/testing.md) — set up test support and write tests for multi-account flows