HIGH broken authenticationphoenixdynamodb

Broken Authentication in Phoenix with Dynamodb

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

Broken Authentication occurs when application functions related to identity management are not implemented correctly, allowing attackers to compromise passwords, keys, or session tokens. In a Phoenix application using Amazon DynamoDB as the user store, the combination of Phoenix’s flexible routing and DynamoDB’s key-based access patterns can introduce subtle authentication weaknesses if developers do not explicitly enforce secure primitives.

One common pattern is storing user records in a DynamoDB table keyed by user_id (partition key) or by email (partition key). If an endpoint such as /api/users/reset accepts an email or username in the request body and queries DynamoDB without first verifying the requester’s authentication context, it may leak whether an account exists or allow enumeration attacks. For example, an attacker can probe with arbitrary emails and observe differences in response timing or status codes, inferring valid accounts.

Phoenix controllers often use Ecto for relational databases, but when DynamoDB is used directly (e.g., via the AWS SDK for Erlang/Elixir), developers are responsible for building secure access patterns. If session tokens or one-time codes are stored in DynamoDB without proper encryption at rest or strict conditional writes, an attacker who gains read access (for example, via a misconfigured IAM policy) can steal active sessions. A typical vulnerable implementation might skip idempotency checks or use DynamoDB conditional writes inconsistently, enabling race conditions that let an attacker reuse a password reset token.

DynamoDB Streams can also contribute to risk if authentication-sensitive events (such as credential updates) are emitted to a stream without validation and are consumed by downstream services that trust the payload. Insecure consumer logic may inadvertently expose change events or allow tampering if message ordering and deduplication are not enforced. Another vector is insufficient enforcement of multi-factor authentication (MFA) in the application layer; DynamoDB may store an MFA flag, but if the flag is not checked consistently across authentication flows, an attacker can bypass MFA requirements.

Finally, unauthenticated endpoints that rely on DynamoDB for lookups can become information disclosure channels. For instance, an endpoint that returns user profile metadata without validating a session may expose sensitive fields (e.g., internal identifiers, roles, or email) when DynamoDB GetItem or Query responses are mapped directly to JSON. These mappings must be explicitly filtered and validated before leaving the node, regardless of how DynamoDB structures the stored item.

Dynamodb-Specific Remediation in Phoenix — concrete code fixes

Secure authentication with DynamoDB in Phoenix requires strict separation of concerns, explicit checks, and safe data handling. Below are concrete, working examples that demonstrate secure patterns.

1. Secure password reset token exchange

Use a cryptographically random token stored with a short TTL and validate ownership without revealing account existence. Store tokens in DynamoDB with a composite key and enforce conditional writes.

defmodule MyApp.Accounts do
  use Aws.Client, service: :dynamodb

  # Store token with condition: token does not already exist
  def store_reset_token(user_id, token_hash, expires_at) do
    params = %{
      table_name: "users",
      key: %{"user_id" => %{s: user_id}},
      update_expression: "SET reset_token = :t, reset_expires = :e",
      condition_expression: "attribute_not_exists(reset_token)",
      expression_attribute_values: %{
        ":t" => %{s: token_hash},
        ":e" => %{n: to_string(expires_at)}
      },
      return_values: "NONE"
    }

    case dynamodb().update_item(params) do
      {:ok, _} -> :ok
      {:error, %{code: "ConditionalCheckFailedException"}} -> {:error, :token_conflict}
      {:error, reason} -> {:error, reason}
    end
  end

  # Verify token and delete in a transaction to prevent reuse
  def consume_reset_token(user_id, token_hash) do
    transact_items = [
      %{
        update: %{
          table_name: "users",
          key: %{"user_id" => %{s: user_id}},
          update_expression: "DELETE SET reset_token, reset_expires",
          condition_expression: "reset_token = :t",
          expression_attribute_values: %{":t" => %{s: token_hash}}
        }
      }
    ]

    case dynamodb().transact_write_items(transaction_items: transact_items) do
      {:ok, _} -> :ok
      {:error, %{code: "ConditionalCheckFailedException"}} -> {:error, :invalid_token}
      {:error, reason} -> {:error, reason}
    end
  end
end

2. Safe user profile retrieval with field-level filtering

Never return raw DynamoDB responses. Map only necessary fields and redact sensitive data before sending to the client.

defmodule MyApp.Accounts do
  def get_public_profile(user_id, requester_id) do
    case user_item(user_id) do
      {:ok, item} when item["user_id"].s == requester_id ->
        # Return full profile to self
        {:ok, filter_public(item)}

      {:ok, item} ->
        # Return limited profile to others
        {:ok, Map.take(item, ["user_id", "display_name", "avatar_url"])}

      error -> error
    end
  end

  defp user_item(user_id) do
    params = %{
      table_name: "users",
      key: %{"user_id" => %{s: user_id}},
      projection_expression: "user_id,display_name,email,avatar_url,mfa_enabled"
    }

    case dynamodb().get_item(params) do
      {:ok, %{item: nil}} -> {:error, :not_found}
      {:ok, %{item: item}} -> {:ok, item}
      {:error, reason} -> {:error, reason}
    end
  end

  defp filter_public(item) do
    Map.take(item, ["user_id", "display_name", "email", "avatar_url", "mfa_enabled"])
  end
end

3. Consistent MFA verification across flows

Store a boolean mfa_enabled attribute and enforce it in a plug that runs before authentication-sensitive actions. Use DynamoDB strongly consistent reads to avoid stale flags.

defmodule MyAppWeb.MfaCheck do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    user_id = conn.private[:current_user_id]

    case fetch_mfa_flag(user_id) do
      {:ok, true} -> conn
      {:ok, false} -> halt_unauthorized(conn, "MFA required")
      _ -> halt_unauthorized(conn, "Forbidden")
    end
  end

  defp fetch_mfa_flag(user_id) do
    params = %{
      table_name: "users",
      key: %{"user_id" => %{s: user_id}},
      projection_expression: "mfa_enabled",
      consistent_read: true
    }

    case dynamodb().get_item(params) do
      {:ok, %{item: %{"mfa_enabled" => %{BOOL: flag}}}} -> {:ok, flag}
      _ -> {:error, :not_found}
    end
  end
end

These patterns address authentication risks by enforcing least privilege on DynamoDB access, avoiding information leakage, and ensuring that security-sensitive attributes are validated on every request.

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

How does middleBrick detect authentication issues when scanning a Phoenix endpoint backed by DynamoDB?
middleBrick runs 12 security checks in parallel, including Authentication, BOLA/IDOR, and Data Exposure. It probes unauthenticated attack surfaces and, when an OpenAPI spec is available, correlates spec definitions with runtime behavior to identify missing auth controls or unsafe DynamoDB mappings that could lead to Broken Authentication.
Can I use the free plan to scan a Phoenix API that uses DynamoDB?
Yes; the free plan provides 3 scans per month and is suitable for trying out detections for Phoenix APIs with DynamoDB. For continuous monitoring or CI/CD integration across many endpoints, the Pro plan includes scheduled scans and GitHub Action PR gates.