Strategy for recovery code authentication.
Allows users to authenticate using one-time recovery codes when they can't access their primary authentication method (e.g., TOTP authenticator app). Recovery codes are single-use and deleted after successful verification.
Requirements
- A separate Ash resource for storing recovery codes with:
- A sensitive string attribute for the hashed code
- A
belongs_torelationship to the user resource create,read, anddestroyactions
- A
has_manyrelationship on the user resource pointing to recovery codes - A brute force protection strategy (rate limiting, audit log, or custom preparation)
Example
defmodule MyApp.Accounts.RecoveryCode do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
domain: MyApp.Accounts
attributes do
uuid_primary_key :id
attribute :code, :string, sensitive?: true, allow_nil?: false
end
relationships do
belongs_to :user, MyApp.Accounts.User, allow_nil?: false
end
actions do
defaults [:read, :destroy]
create :create do
accept [:code]
argument :user_id, :uuid, allow_nil?: false
change manage_relationship(:user_id, :user, type: :append)
end
end
end
defmodule MyApp.Accounts.User do
use Ash.Resource,
extensions: [AshAuthentication],
domain: MyApp.Accounts
relationships do
has_many :recovery_codes, MyApp.Accounts.RecoveryCode
end
authentication do
strategies do
recovery_code do
recovery_code_resource MyApp.Accounts.RecoveryCode
hash_provider AshAuthentication.BcryptProvider
brute_force_strategy {:preparation, MyApp.NoopPreparation}
end
end
end
endActions
The recovery code strategy generates up to two actions:
- verify - Verifies a recovery code for a user. On success, deletes the used code and returns the user. On failure, returns nil.
- generate - When
generate_enabled?is true, generates new recovery codes for a user. Deletes any existing codes and returns the plaintext codes.
authentication.strategies.recovery_code
recovery_code name \\ :recovery_codeAdds recovery code authentication for account recovery when TOTP is unavailable.
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
name | atom | Uniquely identifies the strategy. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
recovery_code_resource | module | The Ash resource module that stores recovery codes. Must have a code attribute and a belongs_to relationship to the user. | |
brute_force_strategy | :rate_limit | {:audit_log, atom} | {:preparation, module} | How you are mitigating brute-force recovery code checks. | |
recovery_codes_relationship_name | atom | :recovery_codes | The name of the has_many relationship on the user resource that points to recovery codes. |
code_field | atom | :code | The name of the attribute on the recovery code resource that stores the hashed code. |
user_relationship_name | atom | :user | The name of the belongs_to relationship on the recovery code resource that points to the user. |
hash_provider | module | AshAuthentication.SHA256Provider | The hash provider to use for hashing and verifying recovery codes. Defaults to AshAuthentication.SHA256Provider which requires codes with at least 60 bits of entropy. For shorter codes, use AshAuthentication.BcryptProvider or AshAuthentication.Argon2Provider. |
code_alphabet | String.t | "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" | The set of characters used when generating recovery codes. Each character in the string must be unique. |
recovery_code_count | pos_integer | 10 | The number of recovery codes to generate. |
code_length | pos_integer | 12 | The length of each generated recovery code. |
verify_action_name | atom | The name to use for the verify action. Defaults to verify_with_<strategy_name>. | |
generate_enabled? | boolean | true | Whether to generate the generate action. Set to false if you want to handle code generation yourself. |
generate_action_name | atom | The name to use for the generate action. Defaults to generate_<strategy_name>_codes. | |
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. |