AshAuthCode

Code-based authentication strategy for Ash Authentication.

Instead of magic links (clicking a URL), users receive a short numeric code via email or SMS and enter it to authenticate.

Installation

Add to your dependencies:

def deps do
  [
    {:ash_auth_code, "~> 0.1.0"}
  ]
end

Usage

Add the extension to your user resource:

defmodule MyApp.Accounts.User do
  use Ash.Resource,
    extensions: [AshAuthentication, AshAuthCode],
    domain: MyApp.Accounts

  authentication do
    tokens do
      enabled? true
      token_resource MyApp.Accounts.Token
      signing_secret fn _, _ -> Application.fetch_env!(:my_app, :token_signing_secret) end
    end

    strategies do
      auth_code do
        identity_field :email
        code_length 6
        token_lifetime {10, :minutes}
        registration_enabled? true

        sender fn email, code, _opts ->
          MyApp.Emails.send_auth_code(email, code)
        end
      end
    end
  end

  attributes do
    uuid_primary_key :id
    attribute :email, :ci_string, allow_nil?: false
  end

  identities do
    identity :unique_email, [:email]
  end
end

How It Works

  1. Request Phase: User submits their email

    • Strategy generates a JWT token
    • Derives a short numeric code from the token (e.g., 847291)
    • Calls your sender with the code (not the token)
    • Returns the token for server-side storage (e.g., in a cookie)
  2. Verify Phase: User enters the code they received

    • Strategy receives both the token (from cookie) and code (from user)
    • Verifies the code matches the token
    • Signs in or registers the user

Configuration Options

Option Type Default Description
identity_field atom :email Field that uniquely identifies the user
code_length integer 6 Length of the numeric code (4-10)
token_lifetime integer or tuple {10, :minutes} How long the token is valid
registration_enabled? boolean false Allow new user registration
single_use_token? boolean true Revoke token after use
sender function required Function to send the code

Sender Function

The sender receives:

sender fn identity, code, opts ->
  email = if is_binary(identity), do: identity, else: identity.email
  MyApp.Emails.send_auth_code(email, code)
end

HTTP Endpoints

The strategy creates two endpoints:

License

MIT