AshAuthentication.HashProvider behaviour (ash_authentication v5.0.0-rc.7)

Copy Markdown View Source

A behaviour providing password hashing.

Summary

Callbacks

Whether the same input always produces the same hash output.

Given some user input as a string, convert it into it's hashed form.

Hash with action context.

The minimum bits of input entropy required for this hash provider to be safe.

Attempt to defeat timing attacks by simulating a password hash check.

Check if the user input matches the hash.

Functions

Calls hash/2 if the provider implements it, otherwise falls back to hash/1.

Callbacks

deterministic?()

@callback deterministic?() :: boolean()

Whether the same input always produces the same hash output.

Deterministic providers (e.g. SHA-256) allow atomic database-level verification by hashing the input and matching directly against stored values. Non-deterministic providers (e.g. bcrypt, argon2) use random salts, so verification requires loading stored hashes and comparing individually.

hash(input)

@callback hash(input :: String.t()) :: {:ok, hash :: String.t()} | :error

Given some user input as a string, convert it into it's hashed form.

hash(input, context)

(optional)
@callback hash(input :: String.t(), context :: map()) ::
  {:ok, hash :: String.t()} | :error

Hash with action context.

An optional variant of hash/1 that receives the Ash context from the current action (changeset or action input). This enables hash providers that need external state — for example, a shared-salt provider can read a pre-generated salt from the context.

When implemented, this callback is preferred over hash/1 by the recovery code strategy's hashing change and verify action.

minimum_entropy()

@callback minimum_entropy() :: non_neg_integer()

The minimum bits of input entropy required for this hash provider to be safe.

Slow hash providers like bcrypt and argon2 return 0 because their computational cost makes brute-force attacks impractical regardless of input entropy. Fast deterministic hash providers like SHA-256 require high-entropy inputs (e.g. 60+ bits) because their speed makes low-entropy inputs vulnerable to offline brute-force attacks.

simulate()

@callback simulate() :: false

Attempt to defeat timing attacks by simulating a password hash check.

See Bcrypt.no_user_verify/1 for more information.

valid?(input, hash)

@callback valid?(input :: String.t(), hash :: String.t()) :: boolean()

Check if the user input matches the hash.

Functions

call_hash(provider, input, context)

@spec call_hash(module(), String.t(), map()) :: {:ok, String.t()} | :error

Calls hash/2 if the provider implements it, otherwise falls back to hash/1.