Brute Force Attack in Phoenix with Bearer Tokens
Brute Force Attack in Phoenix with Bearer Tokens — how this specific combination creates or exposes the vulnerability
A brute force attack against a Phoenix application that uses Bearer tokens typically targets authentication endpoints where tokens are issued or validated. In this scenario, an attacker attempts many token requests or token validation calls to discover valid credentials, session tokens, or to infer account existence. Because Bearer tokens are transmitted in HTTP headers (Authorization: Bearer
Phoenix, implemented with frameworks like Plug or Phoenix.Controller, often relies on Guardian or Pow for token-based authentication. If these libraries are configured without strict rate limits on token verification or issuance endpoints, an attacker can send numerous requests with guessed or stolen tokens. Each request includes the Bearer token in the Authorization header, and the server’s response (e.g., 200 vs 401/403) can reveal whether a token is valid. This behavior enables online brute force or token enumeration, which may lead to account takeover or unauthorized access to protected resources.
Additionally, if token issuance does not adequately bind the token to a scope, device, or one-time use, replay and credential stuffing become more effective. For example, an attacker who obtains a single valid Bearer token might attempt to increment user identifiers or session IDs to explore adjacent accounts (a behavior related to BOLA/IDOR). When combined with weak token entropy or predictable generation, brute force becomes more feasible. The presence of verbose error messages in Phoenix responses can further assist an attacker by distinguishing between a malformed request, an invalid token, or a rate-limited state.
Consider an endpoint like POST /api/v1/auth/verify that expects { "token": "
Bearer Tokens-Specific Remediation in Phoenix — concrete code fixes
To mitigate brute force risks when using Bearer tokens in Phoenix, apply rate limiting at the connection or pipeline level, enforce token entropy and binding, and standardize error responses to avoid information leakage. Below are concrete, realistic examples aligned with typical Phoenix stacks.
Rate limiting with Plug
Use a rate-limiting plug to restrict the number of requests per client IP or token identifier. This example uses :hammer plug to limit authentication-related endpoints.
defmodule MyAppWeb.RateLimiter do
use Plug.Router
require Logger
# Simple in-memory rate limiter (consider Redis for distributed setups)
def init(opts), do: opts
def call(conn, _opts) do
key = conn.ip |> :inet.ntoa() |> to_string()
current = :ets.lookup(:rate_limiter, key) || {0, System.monotonic_time()}
{count, last} = current
now = System.monotonic_time()
window = 1_000_000 * 60 # 1 minute in microseconds
if now - last > window do
:ets.insert(:rate_limiter, {key, {1, now}})
else
if count >= 10 do
Logger.warn("Rate limit exceeded for #{key}")
halt_conn(conn, 429, %{"error" => "Too many requests"})
else
:ets.insert(:rate_limiter, {key, {count + 1, last}})
conn
end
end
end
defp halt_conn(conn, status, body) do
conn
|> Plug.Conn.put_resp_content_type("application/json")
|> Plug.Conn.send_resp(status, Jason.encode!(body))
|> Plug.Conn.halt()
end
end
Then plug it into your pipeline in endpoint.ex or a router:
pipeline :auth_protected do
plug MyAppWeb.RateLimiter
plug MyAppWeb.AuthUtils # your token verification logic
end
Secure token verification with Guardian
Ensure token verification returns consistent error messages and does not distinguish between malformed and expired tokens unnecessarily. Here is a simplified verification helper that avoids leaking details.
defmodule MyAppWeb.AuthUtils do
import Plug.Conn
def verify_token(conn) do
case Guardian.Plug.current_resource(conn) do
{:ok, resource} ->
# proceed with authenticated conn
{:ok, conn, resource}
{:error, _reason} ->
# Return generic unauthorized without indicating why
send_resp(conn, 401, Jason.encode!(%{"error" => "Unauthorized"}))
halt(conn)
end
end
end
Token binding and entropy
Bind tokens to a scope and include jti (JWT ID) with one-time usage checks where feasible. When issuing tokens, use strong random generators and include contextual bindings (e.g., client IP or user agent hash). Example token generation with Guardian and additional claims:
defmodule MyAppWeb.Auth do
use Guardian, otp_app: :my_app
def subject_for_token(resource, _claims) do
# resource is typically your user struct
{:ok, to_string(resource.id)}
end
def resource_from_claims(claims) do
id = claims["sub"]
resource = MyApp.Accounts.get_user!(id)
{:ok, resource}
end
def issue_token(user) do
# Include jti and bind to user agent or session context
extra_claims = %{
jti: Ecto.UUID.generate(),
ua_hash: :crypto.hash(:sha256, user.user_agent || "") |> Base.encode16()
}
Guardian.encode_and_sign(user, %{}, extra_claims)
end
end
Consistent error handling
Ensure token introspection and verification endpoints return uniform responses to prevent attackers from inferring token validity. For instance, always return 401 with a generic body regardless of whether the token is malformed, expired, or invalid.
These practices reduce the effectiveness of brute force attempts against Phoenix endpoints that rely on Bearer tokens, aligning with secure authentication patterns recommended for token-based systems.