Authentication factors

Copy Markdown View Source

KMS factors add steps after principal password authentication. A principal can have zero or more enabled factors.

If no factors are enabled, KMS.create_session/3 returns an authenticated session.

If factors are enabled, KMS.create_session/3 returns a pending session plus challenge metadata. The application collects factor input from the user and calls KMS.advance_session/2 until the session is authenticated.

Built-in providers

  • webauthn: passkeys/WebAuthn using server-side FIDO2 verification.
  • totp: production-usable RFC 6238 authenticator-app codes.
  • one_time_code: development/test provider that returns the code in the challenge response.

Factor records

A factor belongs to a principal and has:

  • provider: provider name, e.g. "totp" or "webauthn".
  • label: user-facing label, e.g. "authenticator".
  • position: order used when multiple factors exist.
  • enabled: disabled factors are ignored.
  • config: provider-specific configuration. This field is redacted from inspect output and omitted from HTTP factor responses.

Create a factor:

{:ok, factor} =
  KMS.create_principal_factor("users", "alice", %{
    provider: "totp",
    label: "authenticator",
    position: 1,
    config: config
  })

Remote HTTP API:

POST /api/principal_factors/create

Authentication flow

Create a session with password:

{:ok, session} = KMS.create_session("users", "alice", %{password: password})

If factors are enabled, the session is pending:

session.state == "pending"
session.challenge.provider == "totp"

Advance with user input:

{:ok, session} = KMS.advance_session(session.secret, %{code: "123456"})

When all factors complete, the session becomes authenticated.

Rate limits

KMS applies factor controls:

config :kms, :session_factor_attempt_limit, 5
config :kms, :session_factor_rate_limit, [limit: 5, window_ms: :timer.minutes(5)]

session_factor_attempt_limit caps failed factor attempts per pending session.

session_factor_rate_limit is optional fixed-window throttling. Enable it for remote deployments.

Custom providers

Custom providers implement KMS.Principals.FactorProvider.

Provider callbacks:

  • issue/3: creates challenge metadata and a factor secret used to protect pending session private-key material.
  • optional verify/4: verifies user input and returns the factor secret.

Register providers:

config :kms, :factor_providers, %{
  "sms" => MyApp.KMS.SMSFactor
}

Production providers should deliver codes out of band and must not return secret codes in challenge responses.