Recovery Codes (Phoenix Integration)

View Source

This guide explains how to add Phoenix UI for recovery code generation and verification. Recovery codes provide a fallback authentication method when a user's TOTP authenticator app is unavailable.

Prerequisites

  1. Recovery code strategy configured on your user resource (see the Recovery Codes tutorial)
  2. AshAuthentication.Phoenix installed and configured
  3. TOTP strategy configured (recovery codes are typically used alongside TOTP)

Built-in Routes

AshAuthentication.Phoenix provides two router macros for recovery codes:

# lib/my_app_web/router.ex
defmodule MyAppWeb.Router do
  use MyAppWeb, :router
  use AshAuthentication.Phoenix.Router

  pipeline :browser do
    # ...
    plug :load_from_session
    plug :set_actor, :user
  end

  scope "/", MyAppWeb do
    pipe_through :browser

    # Standard auth routes
    auth_routes AuthController, MyApp.Accounts.User, path: "/auth"

    # TOTP routes
    totp_2fa_route MyApp.Accounts.User, :totp, auth_routes_prefix: "/auth"
    totp_setup_route MyApp.Accounts.User, :totp, auth_routes_prefix: "/auth"

    # Recovery code routes
    recovery_code_verify_route MyApp.Accounts.User, :recovery_code, auth_routes_prefix: "/auth"
    recovery_code_display_route MyApp.Accounts.User, :recovery_code, auth_routes_prefix: "/auth"
  end
end

recovery_code_verify_route

Generates a recovery code verification page. Supports two modes:

  • Token-based (/recovery-code-verify/:token) — used after password sign-in as a 2FA fallback, when the user clicks "Use a recovery code instead" on the TOTP verify page.
  • Step-up (/recovery-code-verify) — used when an already-authenticated user needs to re-verify their identity.

recovery_code_display_route

Generates a page for generating and displaying recovery codes (/recovery-codes). This should be placed behind authentication middleware so only authenticated users can access it.

Route Options

Both macros accept the same options:

OptionDefaultDescription
path/recovery-code-verify or /recovery-codesPath to mount at
live_viewBuilt-in LiveViewCustom LiveView module
auth_routes_prefixPrefix for auth routes (e.g. "/auth")
overrides[Default]Override modules for customisation
gettext_fnTranslation function as {module, function}
gettext_backendGettext backend as {module, domain}

Cross-Linking with TOTP

When both TOTP and recovery codes are configured, the verify pages should link to each other so users can switch between authentication methods.

Add these overrides to your auth overrides module:

# lib/my_app_web/auth_overrides.ex
defmodule MyAppWeb.AuthOverrides do
  use AshAuthentication.Phoenix.Overrides

  # Show "Use a recovery code instead" on the TOTP verify page
  override AshAuthentication.Phoenix.Components.Totp.Verify2faForm do
    set :recovery_code_link_path, "/recovery-code-verify"
  end

  # Show "Use authenticator app instead" on the recovery code verify page
  override AshAuthentication.Phoenix.Components.RecoveryCode.VerifyForm do
    set :totp_link_path, "/totp-verify"
  end
end

Automatic setup with Igniter

When using mix igniter.install ash_authentication_phoenix --auth-strategy recovery_code, these overrides are automatically added to your auth overrides module.

The cross-links automatically preserve the authentication token in the URL, so users can switch between TOTP and recovery code verification without losing their session state.

Auth Controller Integration

When the recovery code strategy is installed via Igniter alongside TOTP, a success/4 clause is added to the auth controller that redirects users to the recovery codes page after completing TOTP setup:

# Added automatically by the igniter
def success(conn, {_, :confirm_setup}, user, token) do
  conn
  |> store_in_session(user)
  |> set_live_socket_id(token)
  |> assign(:current_user, user)
  |> redirect(to: ~p"/recovery-codes")
end

This ensures users are prompted to generate recovery codes immediately after setting up their authenticator app.

Authentication Metadata

After successful recovery code verification, metadata is attached to the user:

# Strategies used in this session
user.__metadata__.authentication_strategies
#=> [:recovery_code]

# Timestamp of recovery code verification
user.__metadata__.recovery_code_used_at
#=> ~U[2026-04-09 02:00:00Z]

This metadata is persisted in the session and can be used to detect when a recovery code was used (e.g. to prompt the user to set up a new authenticator).

Requiring Recovery Codes

Using the Plug

For controller-based routes:

pipeline :require_recovery_codes do
  plug AshAuthentication.Phoenix.Plug.RequireRecoveryCodes,
    resource: MyApp.Accounts.User,
    on_missing: :redirect_to_setup,
    setup_path: "/recovery-codes"
end
OptionDefaultDescription
resourceRequiredThe user resource module
on_missing:halt:halt, :redirect_to_setup, or {:redirect, path}
setup_path"/recovery-codes"Path to redirect for setup
current_user_assign:current_userAssign key for current user

Using the LiveView Hook

For LiveView routes:

live_session :require_recovery_codes,
  on_mount: [
    {AshAuthentication.Phoenix.LiveSession, :default},
    {AshAuthentication.Phoenix.LiveSession.RequireRecoveryCodes, :require_recovery_codes}
  ] do
  live "/secure", SecureLive
end

Checking Recovery Code Status

Use RecoveryCodeHelpers in controllers, LiveViews, and templates:

alias AshAuthentication.Phoenix.RecoveryCodeHelpers

# Check if user has recovery codes generated
RecoveryCodeHelpers.recovery_codes_configured?(user)
#=> true

# Check if resource supports recovery codes
RecoveryCodeHelpers.recovery_code_available?(MyApp.Accounts.User)
#=> true

# Get the recovery code strategy
{:ok, strategy} = RecoveryCodeHelpers.get_recovery_code_strategy(MyApp.Accounts.User)

Next Steps