KeenAuth.EmailAuthenticationHandler behaviour (KeenAuth v1.0.1)

Copy Markdown View Source

Behaviour for implementing email/password authentication.

This is the entry point for email-based authentication, equivalent to what OAuth providers (via Assent) do for OAuth flows. Your implementation verifies credentials and returns a raw user map that flows through the KeenAuth pipeline.

Pipeline Position


 EmailAuthHandler        YOU IMPLEMENT THIS
 (credential verify)  

            {:ok, raw_user}
           

 Mapper                 Normalize to KeenAuth.User

           

 Processor              Business logic, DB ops

           

 Storage                Persist session

Callbacks

Example Implementation

defmodule MyApp.Auth.EmailHandler do
  @behaviour KeenAuth.EmailAuthenticationHandler
  import Plug.Conn
  import Phoenix.Controller

  @impl true
  def authenticate(_conn, %{"email" => email, "password" => password}) do
    with {:ok, user} <- MyApp.Accounts.get_user_by_email(email),
         :ok <- MyApp.Accounts.verify_password(user, password) do
      # Return raw user map - will be passed to Mapper
      {:ok, %{
        "sub" => to_string(user.id),
        "email" => user.email,
        "name" => user.display_name,
        "preferred_username" => user.username
      }}
    else
      _ -> {:error, :invalid_credentials}
    end
  end

  @impl true
  def handle_authenticated(conn, user) do
    # Optional: audit log, analytics, etc.
    Logger.info("User #{user.email} signed in via email")
    conn
  end

  @impl true
  def handle_unauthenticated(conn, params, {:error, :invalid_credentials}) do
    conn
    |> put_flash(:error, "Invalid email or password")
    |> redirect(to: params["redirect_to"] || "/login")
  end

  def handle_unauthenticated(conn, _params, _other_error) do
    conn
    |> put_flash(:error, "Authentication failed")
    |> redirect(to: "/login")
  end
end

Configuration

config :keen_auth,
  email_enabled: true,
  strategies: [
    email: [
      authentication_handler: MyApp.Auth.EmailHandler,
      mapper: KeenAuth.Mapper.Default,
      processor: MyApp.Auth.Processor
    ]
  ]

Summary

Callbacks

Verify user credentials and return raw user data.

Called after successful authentication, before redirect.

Handle failed authentication attempts.

Callbacks

authenticate(conn, params)

@callback authenticate(conn :: Plug.Conn.t(), params :: map()) ::
  {:ok, map()} | {:error, term()}

Verify user credentials and return raw user data.

Called when a user submits the login form. Should validate the email/password and return a map with user data that will be passed to the Mapper.

Return Values

  • {:ok, user_map} - Credentials valid. user_map should include keys like "sub", "email", "name" that your Mapper expects.
  • {:error, reason} - Credentials invalid. reason is passed to handle_unauthenticated/3.

handle_authenticated(conn, user)

@callback handle_authenticated(conn :: Plug.Conn.t(), user :: KeenAuth.User.t()) ::
  Plug.Conn.t()

Called after successful authentication, before redirect.

Use for side effects like audit logging. The return value should be the conn (potentially modified).

handle_unauthenticated(conn, params, error)

@callback handle_unauthenticated(
  conn :: Plug.Conn.t(),
  params :: map(),
  error :: term()
) :: Plug.Conn.t()

Handle failed authentication attempts.

Called when authenticate/2 returns an error. Should render an error message or redirect the user back to the login page.

The controller adds a random delay before calling this to prevent timing attacks.

Functions

authenticate(conn, params)

current_authentication_handler!(conn)

get_authentication_handler!(config)

handle_authenticated(conn, user)

handle_unauthenticated(conn, params, err)