HIGH broken authenticationphoenixhmac signatures

Broken Authentication in Phoenix with Hmac Signatures

Broken Authentication in Phoenix with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Broken Authentication occurs when application functions related to authentication and session management are implemented incorrectly, enabling attackers to compromise passwords, keys, or session tokens. In Phoenix, using Hmac Signatures for request authentication can introduce subtle vulnerabilities when the signature mechanism is misapplied or when related controls are weak.

Hmac Signatures typically involve generating a cryptographic hash-based message authentication code from a request payload and a shared secret. If the signature is computed over only part of the request—such as the body but excluding critical headers like timestamps, nonces, or API keys—an attacker can reuse a valid signature across requests by altering the unprotected elements. This is especially risky when endpoints accept mutable or predictable parameters such as user identifiers or resource IDs without signature coverage, enabling IDOR or BOLA-style attacks.

In a Phoenix API, consider a common pattern where a client sends an HTTP request with an authorization header containing a hex-encoded HMAC (e.g., Hmac-SHA256) of the request body using a shared client secret. If the server does not enforce strict method and path consistency, and if it also relies on unauthenticated route parameters to locate the resource, an attacker can intercept or modify the URL to access another user’s resource while presenting a valid signature for the body. This mismatch between what is signed and what is trusted leads to Broken Authentication and can be discovered by scanners that test unauthenticated attack surfaces.

Additionally, weak handling of replay attacks compounds the issue. Without a nonce or timestamp included in the signed string, an attacker can capture a valid request and replay it within the server’s validity window to perform unauthorized actions. Phoenix applications that use Hmac Signatures should ensure the signature includes a short-lived timestamp and, where applicable, a nonce to prevent reuse. Without these, the authentication boundary is effectively limited to the shared secret alone, which is insufficient when endpoints expose sensitive operations without additional controls.

Another contributing factor is improper secret management. If the shared Hmac secret is stored in configuration files exposed to version control or if it is transmitted or logged inadvertently, the integrity of the entire scheme collapses. Combined with unvalidated input and missing rate limiting, this can enable credential leakage or brute-force attempts. Security scans that include authentication and authorization checks alongside input validation and rate limiting are better equipped to surface these interrelated weaknesses in the unauthenticated attack surface.

Hmac Signatures-Specific Remediation in Phoenix — concrete code fixes

To remediate Broken Authentication when using Hmac Signatures in Phoenix, align the signed payload with the full context of the request and enforce strict validation on server side. The following examples show a secure approach that includes method, path, timestamp, nonce, and body in the signature, and verifies these elements on each request.

First, client-side signature generation. The client constructs the string to sign from components that the server will also validate. Note the inclusion of timestamp and nonce to prevent replay, and canonical ordering to avoid ambiguity:

timestamp = System.system_time(:second) |> Integer.to_string()
nonce = :crypto.strong_rand_bytes(16) |> Base.url_encode64(padding: false)
body = Jason.encode!(%{user_id: "123", action: "update"})
string_to_sign = ["POST", "/api/v1/resource", timestamp, nonce, body] |> Enum.join("\n")
signature = :crypto.mac(:hmac, :sha256, secret_key, string_to_sign)
headers = %{
  "authorization" => "Hmac-SHA256 key=\"#{client_key}\", timestamp=\"#{timestamp}\", nonce=\"#{nonce}\", signature=\"#{Base.url_encode64(signature, padding: false)}\"",
  "content-type" => "application/json"
}

On the server side in Phoenix, a plug validates the signature, timestamp freshness, and nonce uniqueness. The plug retrieves the client key, reconstructs the same string to sign, and compares the signatures in constant time to avoid timing attacks:

defmodule MyAppWeb.HmacAuthPlug do
  import Plug.Conn
  import Phoenix.Controller, only: [json: 2, put_status: 2]

  def init(opts), do: opts

  def call(conn, _opts) do
    with ["Hmac-SHA256 key=\"" <> key_id <> "\", timestamp=\"" <> ts <> "\", nonce=\"" <> nonce <> "\", signature=\"" <> received_sig <> "\"]" <- get_req_header(conn, "authorization"),
         {:ok, secret} <- fetch_secret(key_id),
         {:ok, true} <- verify_timestamp(ts),
         {:ok, true} <- verify_nonce(nonce),
         true <- valid_signature?(conn, secret, ts, nonce, received_sig) do
      assign(conn, :authenticated, true)
    else
      _ -> send_resp(conn, 401, "Unauthorized") |> halt()
    end
  end

  defp verify_timestamp(ts, max_age \\ 30) do
    with {ts_int, ""} <- Integer.parse(ts),
         now <- System.system_time(:second),
         diff <- abs(now - ts_int),
         true <- diff <= max_age do
      {:ok, true}
    else
      _ -> {:ok, false}
    end
  end

  defp verify_nonce(nonce) do
    # Use a distributed cache or DB to ensure uniqueness within a window
    case MyApp.NonceStore.check_and_insert(nonce) do
      {:ok, true} -> {:ok, true}
      _ -> {:ok, false}
    end
  end

  defp valid_signature?(conn, secret, ts, nonce, received_sig) do
    body = case conn.body_params do
      %{} = p -> p |> Jason.encode!()
      _ -> ""
    end
    string_to_sign = ["#{conn.method}", conn.request_path, ts, nonce, body] |> Enum.join("\n")
    expected_sig = :crypto.mac(:hmac, :sha256, secret, string_to_sign)
    # Constant-time compare
    :crypto.verify(:hmac, :sha256, secret, string_to_sign, Base.url_decode64!(received_sig <> "=="))
  end

  defp fetch_secret(key_id) do
    # Retrieve secret securely from a vault or encrypted configuration
    case MyApp.SecretBackend.get(key_id) do
      {:ok, secret} -> {:ok, secret}
      _ -> {:error, :not_found}
    end
  end
end

In your router, plug the HmacAuthPlug before the action handling the endpoint to enforce authenticated requests:

pipeline :api do
  plug MyAppWeb.HmacAuthPlug
  plug :accepts, ["json"]
end

scope "/api", MyAppWeb do
  pipe_through :api

  put "/resource/:id", ResourceController, :update
end

These changes ensure that the signature covers the full request context, that replay windows are limited, and that secrets are retrieved securely. By combining these practices with continuous monitoring and scans that include authentication and input validation, you reduce the surface for Broken Authentication when using Hmac Signatures in Phoenix.

Related CWEs: authentication

CWE IDNameSeverity
CWE-287Improper Authentication CRITICAL
CWE-306Missing Authentication for Critical Function CRITICAL
CWE-307Brute Force HIGH
CWE-308Single-Factor Authentication MEDIUM
CWE-309Use of Password System for Primary Authentication MEDIUM
CWE-347Improper Verification of Cryptographic Signature HIGH
CWE-384Session Fixation HIGH
CWE-521Weak Password Requirements MEDIUM
CWE-613Insufficient Session Expiration MEDIUM
CWE-640Weak Password Recovery HIGH

Frequently Asked Questions

Why does including timestamp and nonce in the Hmac signature help prevent Broken Authentication?
Including a timestamp and nonce in the signed string prevents replay attacks and ensures request uniqueness. The server can reject stale timestamps and track used nonces to stop the same signed request from being reused, which is especially important when the signature does not cover URL parameters that may be manipulated.
What should I do if my Phoenix endpoint accepts mutable route parameters when using Hmac Signatures?
Include the relevant path segments used for resource identification in the signature string, or avoid using mutable route parameters for authorization decisions. Validate that the authenticated subject matches the target resource on the server, and enforce checks beyond the signature to prevent IDOR or BOLA.