# `AshAuthentication.Strategy.Otp`
[🔗](https://github.com/team-alembic/ash_authentication/blob/main/lib/ash_authentication/strategies/otp.ex#L5)

Strategy for authentication using a one-time password (OTP).

In order to use OTP authentication your resource needs to meet the
following minimum requirements:

1. Have a primary key.
2. A uniquely constrained identity field (eg `username` or `email`)
3. Have tokens enabled.

There are other options documented in the DSL.

### Example

```elixir
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
end
```

## Actions

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"})
    :ok

Signing 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.

# `t`

```elixir
@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(),
  provider: :otp,
  registration_enabled?: boolean(),
  request_action_name: atom(),
  resource: module(),
  sender: {module(), keyword()},
  sign_in_action_name: atom(),
  single_use_token?: boolean()
}
```

# `compute_deterministic_jti`

```elixir
@spec compute_deterministic_jti(t(), String.t(), String.t()) :: String.t()
```

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_deterministic_jti_for_identity`

```elixir
@spec compute_deterministic_jti_for_identity(t(), String.t(), String.t()) ::
  String.t()
```

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.

# `generate_otp_token_for`

```elixir
@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.

# `generate_otp_token_for_identity`

```elixir
@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_otp`

```elixir
@spec normalize_otp(t(), String.t()) :: String.t()
```

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.

# `transform`

# `verify`

---

*Consult [api-reference.md](api-reference.md) for complete listing*
