Strategy for Time-based One-Time Password (TOTP) authentication.
Provides TOTP support via nimble_totp, allowing users to authenticate using time-based codes from authenticator apps like Google Authenticator, Authy, or 1Password.
Requirements
Your resource needs to meet the following minimum requirements:
- Have a primary key.
- An identity field (e.g.,
emailorusername) for identifying users. - A sensitive binary field for storing the TOTP secret.
- A sensitive datetime field for tracking the last successful TOTP authentication.
- A brute force protection strategy (rate limiting, audit log, or custom preparation).
Example
defmodule MyApp.Accounts.User do
use Ash.Resource,
extensions: [AshAuthentication],
domain: MyApp.Accounts
attributes do
uuid_primary_key :id
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :totp_secret, :binary, sensitive?: true
attribute :last_totp_at, :utc_datetime, sensitive?: true
end
authentication do
tokens do
enabled? true
token_resource MyApp.Accounts.Token
end
strategies do
totp do
identity_field :email
issuer "MyApp"
brute_force_strategy {:audit_log, :my_audit_log}
end
end
add_ons do
audit_log :my_audit_log do
audit_log_resource MyApp.Accounts.AuditLog
log_actions [:sign_in_with_totp, :verify_with_totp, :confirm_setup_with_totp]
end
end
end
identities do
identity :unique_email, [:email]
end
endActions
The TOTP strategy can generate up to four actions:
- setup - Generates a new TOTP secret for the user. Returns the user with
a
totp_urlcalculation that can be rendered as a QR code. - confirm_setup - When
confirm_setup_enabled?is true, this action verifies a TOTP code before activating the secret. Requires tokens to be enabled. - sign_in - Authenticates a user using their identity and a TOTP code.
- verify - Checks if a TOTP code is valid for a given user (without signing in).
Brute Force Protection
TOTP codes have a small keyspace (typically 6 digits), making them vulnerable
to brute force attacks. You must configure a brute_force_strategy:
:rate_limit- UsesAshRateLimiterto limit attempts.{:audit_log, :audit_log_name}- Uses an audit log to track failed attempts.{:preparation, ModuleName}- Custom preparation for rate limiting.
Working with Actions
You can interact with TOTP actions via the AshAuthentication.Strategy protocol:
iex> strategy = AshAuthentication.Info.strategy!(MyApp.Accounts.User, :totp)
...> {:ok, user} = AshAuthentication.Strategy.action(strategy, :setup, %{user: existing_user})
...> user.totp_url_for_totp # QR code URL
iex> {:ok, true} = AshAuthentication.Strategy.action(strategy, :verify, %{user: user, code: "123456"})
Summary
Functions
Callback implementation for AshAuthentication.Strategy.Custom.transform/2.
Callback implementation for AshAuthentication.Strategy.Custom.verify/2.
Types
@type t() :: %AshAuthentication.Strategy.Totp{ __identifier__: any(), __spark_metadata__: any(), audit_log_max_failures: pos_integer(), audit_log_window: pos_integer() | {pos_integer(), :days | :hours | :minutes | :seconds}, brute_force_strategy: :rate_limit | {:audit_log, atom()} | {:preparation, module()}, confirm_setup_action_name: atom(), confirm_setup_enabled?: boolean(), grace_period: non_neg_integer() | nil, identity_field: atom(), issuer: String.t(), last_totp_at_field: atom(), name: atom(), period: pos_integer(), provider: :totp, read_secret_from: atom() | nil, resource: Ash.Resource.t(), secret_field: atom(), secret_length: pos_integer(), setup_action_name: atom(), setup_enabled?: boolean(), setup_token_lifetime: pos_integer() | {pos_integer(), :days | :hours | :minutes | :seconds}, sign_in_action_name: atom(), sign_in_enabled?: boolean(), totp_url_field: atom(), verify_action_name: atom(), verify_enabled?: boolean() }
Functions
Callback implementation for AshAuthentication.Strategy.Custom.transform/2.
Callback implementation for AshAuthentication.Strategy.Custom.verify/2.