Strategy for authentication using a one-time password (OTP).
In order to use OTP authentication your resource needs to meet the following minimum requirements:
- Have a primary key.
- A uniquely constrained identity field (eg
usernameoremail) - Have tokens enabled.
There are other options documented in the DSL.
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
end
authentication do
tokens do
enabled? true
store_all_tokens? true
token_resource MyApp.Accounts.Token
signing_secret MyApp.Secrets
end
strategies do
otp do
identity_field :email
brute_force_strategy :rate_limit
otp_lifetime {10, :minutes}
otp_length 6
otp_characters :unambiguous_uppercase
sender MyApp.OtpSender
end
end
end
identities do
identity :unique_email, [:email]
end
endActions
By default the OTP strategy will automatically generate the request and sign-in actions for you, however you're free to define them yourself. If you do, then the action will be validated to ensure that all the needed configuration is present.
If you wish to work with the actions directly from your code you can do so via
the AshAuthentication.Strategy protocol.
Examples
Requesting that an OTP code is sent for a user:
iex> strategy = Info.strategy!(Example.UserWithOtp, :otp)
...> Strategy.action(strategy, :request, %{"email" => "user@example.com"})
:okSigning in using an OTP code:
iex> strategy = Info.strategy!(Example.UserWithOtp, :otp)
...> {:ok, user} = Strategy.action(strategy, :sign_in, %{"email" => "user@example.com", "otp" => "ABCDEF"})Plugs
The OTP strategy provides plug endpoints for both request and sign-in actions.
If you wish to work with the plugs directly, you can do so via the
AshAuthentication.Strategy protocol.
Summary
Functions
Compute a deterministic JTI from the strategy name, user subject, and normalized OTP code.
Compute a deterministic JTI from the strategy name, an identity value, and normalized OTP code.
Generate a JWT with a deterministic JTI for the given OTP code and store it in the token resource.
Generate a JWT with a deterministic JTI for an identity value (not a specific user).
Normalize an OTP code using the strategy's generator.
Callback implementation for AshAuthentication.Strategy.Custom.transform/2.
Callback implementation for AshAuthentication.Strategy.Custom.verify/2.
Types
@type t() :: %AshAuthentication.Strategy.Otp{ __spark_metadata__: Spark.Dsl.Entity.spark_meta(), 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()}, case_sensitive?: boolean(), identity_field: atom(), lookup_action_name: atom() | nil, name: atom(), otp_characters: :unambiguous_uppercase | :unambiguous_alphanumeric | :digits_only | :uppercase_letters_only, otp_generator: module() | nil, otp_length: pos_integer(), otp_lifetime: pos_integer() | {pos_integer(), atom()}, otp_param_name: atom(), registration_enabled?: boolean(), request_action_name: atom(), resource: module(), sender: {module(), keyword()}, sign_in_action_name: atom(), single_use_token?: boolean() }
Functions
Compute a deterministic JTI from the strategy name, user subject, and normalized OTP code.
This allows us to store a JWT with a known JTI and later look it up using only
the submitted OTP code (without needing the original JWT). Hashing is delegated
to AshAuthentication.SHA256Provider so the crypto approach stays consistent
with other strategies.
Compute a deterministic JTI from the strategy name, an identity value, and normalized OTP code.
Used when registration_enabled? is true, since the user may not exist yet
and we don't have a subject. The identity value (e.g. email) is used instead.
@spec generate_otp_token_for(t(), Ash.Resource.record(), String.t(), keyword(), map()) :: {:ok, binary()} | :error
Generate a JWT with a deterministic JTI for the given OTP code and store it in the token resource.
The generated JWT is for internal bookkeeping only (it is never sent to the user). The OTP code itself is sent to the user via the sender.
@spec generate_otp_token_for_identity(t(), String.t(), String.t(), keyword(), map()) :: {:ok, binary()} | :error
Generate a JWT with a deterministic JTI for an identity value (not a specific user).
Used when registration_enabled? is true. The JTI is derived from the identity
value so it can be recomputed during sign-in without needing a user record.
Normalize an OTP code using the strategy's generator.
When case_sensitive? is false (the default), the code is uppercased
so that "xkptmh" matches "XKPTMH". When true, only whitespace
trimming is applied.
Callback implementation for AshAuthentication.Strategy.Custom.transform/2.
Callback implementation for AshAuthentication.Strategy.Custom.verify/2.