HIGH bola idorphoenixapi keys

Bola Idor in Phoenix with Api Keys

Bola Idor in Phoenix with Api Keys — how this specific combination creates or exposes the vulnerability

Broken Object Level Authorization (BOLA) occurs when an API fails to enforce authorization checks between a user and a specific object they are trying to access. In Phoenix, a common pattern is to identify resources (e.g., a user profile, an invoice, or a document) using a public identifier such as an integer or UUID, and to rely on an API key for authentication. When API keys are used without additional ownership checks, BOLA vulnerabilities emerge.

Consider a Phoenix endpoint designed to retrieve a user’s profile: GET /api/v1/users/123. The request includes an API key header for identification, but the server does not verify that the profile ID 123 belongs to the principal associated with the provided API key. Because API keys are often long-lived and stored with higher privileges than session tokens, this misalignment between authentication and authorization makes the attack surface severe. An attacker who obtains or guesses another user’s ID can enumerate or modify resources across accounts, despite being authenticated with a valid key.

In practice, this can happen when developers use API keys for convenience but neglect to scope queries by the authenticated principal. For example, a simple Ecto query like Repo.get(User, id) without a tenant or user context allows horizontal privilege escalation across users. If the API key is leaked in logs, client-side storage, or referrer headers, the risk compounds because the key itself does not rotate as frequently as session tokens. The vulnerability maps directly to OWASP API Top 10 A1: Broken Object Level Authorization and can lead to unauthorized data access or modification, violating principles of least privilege and data isolation.

Phoenix APIs that expose numeric or predictable IDs without verifying ownership are especially prone to this class of issue. Even when rate limiting and input validation are present, BOLA persists because those controls do not address the missing ownership check. Real-world patterns include invoice services where /invoices/45 is accessible with a valid key but lacks a join to confirm the invoice belongs to the authenticated account. The same applies to multi-tenant systems where a shared API key is used across services without clear tenant boundaries, enabling cross-tenant data exposure.

Api Keys-Specific Remediation in Phoenix — concrete code fixes

Remediation focuses on ensuring that every data access decision includes both authentication (the API key) and authorization (ownership or scope). In Phoenix, this typically means enriching the connection with the authenticated principal and scoping queries accordingly.

Example 1: Scoped query with authentication context

Instead of fetching a record by raw ID, derive the query from the authenticated subject. If you use Guardian or a similar library to validate API keys, bind the subject to the connection and use it in queries:

defmodule MyAppWeb.UserController do
  use MyAppWeb, :controller

  alias MyApp.Accounts
  alias MyApp.Accounts.User

  # Assuming Guardian.Plug is used to authenticate via API key and assign current_resource
  plug Guardian.Plug.EnsureAuthenticated when action in [:show, :update]

  def show(conn, %{"id" => id}) do
    # The authenticated subject is available via current_resource from Guardian
    user = Accounts.get_user_for(conn.assigns.current_resource, id)
    case user do
      nil -> send_resp(conn, :not_found, "Not found")
      user -> render(conn, "user.json", user: user)
    end
  end
end

The key is that Accounts.get_user_for/2 uses the authenticated subject (from the API key) to scope the query, ensuring the requested ID belongs to the same tenant or user.

Example 2: Context-aware authorization function

Define a policy function that explicitly checks ownership before returning a record:

defmodule MyApp.Accounts do
  alias MyApp.Repo
  alias MyApp.Accounts.User

  def get_user_for(%{assigns: %{api_key_user: %{tenant_id: tenant_id}}}, id) do
    Repo.get_by(User, id: id, tenant_id: tenant_id)
  end

  def get_user_for(_, _), do: nil
end

Here, the API key validation step populates assigns.api_key_user with minimal claims, including a tenant or subject identifier. The query then filters by both ID and tenant, preventing cross-user reads even when IDs are predictable.

Example 3: Plug-based subject assignment

Create a lightweight plug that resolves the subject from the API key and attaches it to the connection, keeping controllers thin and authorization explicit:

defmodule MyAppWeb.Plugs.ApiKeySubject do
  import Plug.Conn

  def init(opts), do: opts

  def call(conn, _opts) do
    case authenticate_from_header(conn) do
      {:ok, claims} ->
        # Assign a lightweight subject derived from the API key claims
        assign(conn, :api_key_subject, claims.subject)
      {:error, _} ->
        send_resp(conn, :unauthorized, "Invalid API key")
        halt(conn)
    end
  end

  defp authenticate_from_header(conn) do
    with [key] <- get_req_header(conn, "x-api-key"),
         {:ok, claims} <- MyApp.Auth.validate_api_key(key) do
      {:ok, claims}
    else
      _ -> {:error, :unauthorized}
    end
  end
end

Use this plug in your router before protected pipelines:

pipeline :api_auth do
  plug MyAppWeb.Plugs.ApiKeySubject
end

scope "/api", MyAppWeb do
  pipe_through [:api_auth, :ensure_authenticated]

  get "/users/:id", UserController, :show
end

By coupling API key validation with explicit scoping, you eliminate the BOLA condition. This approach aligns with remediation guidance for OWASP API Top 10 and supports mapping findings to frameworks such as PCI-DSS and SOC2.

Related CWEs: bolaAuthorization

CWE IDNameSeverity
CWE-250Execution with Unnecessary Privileges HIGH
CWE-639Insecure Direct Object Reference CRITICAL
CWE-732Incorrect Permission Assignment HIGH

Frequently Asked Questions

How can I detect BOLA in my Phoenix API when using API keys?
Verify that every data retrieval includes a tenant or ownership check that matches the authenticated principal derived from the API key. Review queries to ensure they filter by both ID and subject (e.g., tenant_id), and test by requesting another user’s ID with a valid key; a successful read indicates BOLA.
Do API keys alone provide sufficient protection against BOLA?
No. API keys authenticate requests but do not enforce object-level permissions. You must scope queries to the authenticated principal and validate that the requested resource belongs to the same tenant or subject to prevent horizontal privilege escalation.