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"})authentication.strategies.totp
totp name \\ :totpAdds TOTP-based one-time passcode authentication.
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
name | atom | Uniquely identifies the strategy. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
brute_force_strategy | :rate_limit | {:audit_log, atom} | {:preparation, module} | How you are mitigating brute-force token checks. | |
identity_field | atom | :username | The name of the attribute which uniquely identifies the user, usually something like username or email_address. |
issuer | String.t | The TOTP issuer to use. Defaults to the strategy name. | |
secret_field | atom | :totp_secret | The name of the attribute within which to store the TOTP secret. |
read_secret_from | atom | The attribute or calculation to read the TOTP secret from. Defaults to secret_field. Useful with AshCloak where encrypted values are read via calculations. | |
secret_length | pos_integer | 20 | The number of bytes to use when generating secrets. Default is 20 as per the HOTP RFC. |
last_totp_at_field | atom | :last_totp_at | The name of the attribute or calculation used to track the last successful TOTP time. |
period | pos_integer | 30 | The period (in seconds) in which the code is valid. |
grace_period | non_neg_integer | nil | The number of additional previous time periods to accept when validating TOTP codes. For example, 1 also accepts the previous period's code. See the NimbleTOTP grace period docs. Defaults to nil (no grace period). | |
setup_enabled? | boolean | true | If you do not want the setup action to be generated/validated you disable it by setting this to false. |
setup_action_name | atom | The name to use for the setup action. Defaults to setup_with_<strategy_name>. | |
totp_url_field | atom | The name to use for the TOTP URL calculation. Defaults to totp_url_for_<strategy_name>. | |
sign_in_enabled? | boolean | false | If you do not want users to be able to sign in using this strategy, set this to false. |
sign_in_action_name | atom | The name to use for the sign in action. Defaults to sign_in_with_<strategy_name>. | |
verify_enabled? | boolean | true | If you do not want users to be able to verify their TOTP codes outside of the sign-in action (or you want to handle it yourself), set this to false. |
verify_action_name | atom | The name to use for the verify action. Defaults to verify_with_<strategy_name>. | |
confirm_setup_enabled? | boolean | false | When enabled, setup generates a token that must be confirmed with a valid TOTP code before the secret is stored. |
confirm_setup_action_name | atom | The name to use for the confirm setup action. Defaults to confirm_setup_with_<strategy_name>. | |
setup_token_lifetime | pos_integer | {pos_integer, :days | :hours | :minutes | :seconds} | {10, :minutes} | How long the setup token is valid. If no unit is provided, then minutes is assumed. Defaults to 10 minutes. |
audit_log_window | pos_integer | {pos_integer, :days | :hours | :minutes | :seconds} | {5, :minutes} | Time window for counting failed attempts when using the {:audit_log, ...} brute force strategy. If no unit is provided, then minutes is assumed. Defaults to 5 minutes. |
audit_log_max_failures | pos_integer | 5 | Maximum allowed failures within the window before blocking when using the {:audit_log, ...} brute force strategy. Defaults to 5. |
Introspection
Target: AshAuthentication.Strategy.Totp