Clickjacking in Phoenix with Bearer Tokens
Clickjacking in Phoenix with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side attack that tricks a user into interacting with a hidden or disguised UI element, often by nesting the target site inside an invisible iframe. When a Phoenix application uses Bearer Tokens for authentication—typically sent in the Authorization header—clickjacking can expose privileged actions even when the token itself is not leaked via the DOM. The risk arises because the token is stored in browser memory or in secure HTTP-only cookies and is automatically included by the browser in requests initiated from within the embedded page, provided same-site and cross-origin cookie policies allow it.
In Phoenix, if an endpoint performs sensitive operations (for example, changing an email, upgrading a subscription, or deleting a resource) based solely on the presence of a valid Bearer Token and does not enforce additional UI-level protections, an attacker can craft a page that embeds your Phoenix endpoint in an iframe. The attacker then overlays invisible controls or mimics a legitimate UI on top of the iframe, causing the victim to inadvertently trigger state-changing requests. Because the browser includes the Bearer Token automatically, the forged interaction is executed in the context of the victim’s authenticated session.
This combination is particularly dangerous when:
- The Phoenix app sets session cookies with
SameSite=None; Secureto support cross-origin requests (e.g., for embedded widgets) without also enforcing anti-CSRF measures at the UI layer. - Endpoints rely only on the Authorization header Bearer Token and lack frame-busting headers or X-Frame-Options protections.
- The UI contains interactive elements that can be triggered via GET requests or predictable POST payloads, enabling automated click sequences via CSS or JavaScript within the iframe.
For example, consider a Phoenix endpoint that accepts a POST to /api/v1/users/email with a JSON body and an Authorization Bearer Token. An attacker can host a page with an invisible iframe pointing to that endpoint and overlay a button styled to look harmless. When clicked, the user unknowingly changes their email address, and the request carries the victim’s token automatically. The attack does not require token exfiltration; it exploits the trust placed in the browser’s automatic credential handling and the absence of frame isolation.
Because middleBrick scans the unauthenticated attack surface, it can detect missing anti-clickjacking headers and misconfigured frame policies during a standard scan. This helps teams identify exposed endpoints where Bearer Token authentication is not complemented by UI-level defenses.
Bearer Tokens-Specific Remediation in Phoenix — concrete code fixes
Remediation focuses on layering defenses so that even if a request originates from an embedded context, it does not result in unintended actions. The key is to avoid relying solely on the Bearer Token’s presence and to enforce explicit UI-origin checks and strict same-site cookie policies.
1. Set SameSite and Secure Cookie Attributes
Ensure session and authentication cookies use SameSite=Lax or SameSite=Strict and Secure. For APIs using Bearer Tokens in Authorization headers, this prevents cookies from being sent in cross-site requests, reducing the impact of clickjacking when tokens are tied to cookies.
# In config/runtime.ex or a similar configuration module
defmodule MyAppWeb.Endpoint do
plug Plug.Session,
store: :cookie,
key: "_myapp_key",
signing_salt: "signing_salt_here",
same_site: "Lax",
secure: true
end
2. Explicit Frame-Busting and X-Frame-Options
Add headers to prevent the application from being embedded in iframes. In Phoenix, you can use a plug to set X-Frame-Options and inject a frame-ancestors CSP directive.
# In a plug pipeline or router
pipeline :browser do
plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json]
plug Phoenix.Controller.set_session, secure: true
plug :set_frame_options
end
defp set_frame_options(conn, _opts) do
put_resp_header(conn, "x-frame-options", "DENY")
|> put_resp_header("content-security-policy", "frame-ancestors 'self'")
end
3. Require Anti-CSRF Tokens for State-Changing Requests
Even when using Bearer Tokens in the Authorization header, treat sensitive POST, PUT, PATCH, and DELETE requests as state-changing and enforce origin validation. Phoenix does not enable CSRF protection by default for API pipelines, so add explicit checks for custom headers or Origin validation.
# In a controller that handles sensitive actions
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
action_fallback MyAppWeb.FallbackController
plug :validate_csrf_header when action in [:update_email, :delete_account]
def update_email(conn, %{"email" => email}) do
# business logic
end
defp validate_csrf_header(conn, _) do
case get_req_header(conn, "x-csrf-token") do
[token] when token == get_session(conn, :csrf_token) -> conn
_ -> Plug.Conn.send_resp(conn, 403, "Invalid request origin") |> halt()
end
end
end
4. Validate Referer and Origin Headers
For endpoints that accept Bearer Tokens via Authorization headers, validate the Referer or Origin header to ensure requests originate from your own frontend. This is not a standalone defense due to header spoofing in some environments, but it adds a layer when combined with SameSite cookies and CSRF tokens.
plug :validate_request_origin when action in [:create, :update, :delete]
defp validate_request_origin(conn, _) do
allowed_origin = "https://app.mycompany.com"
case get_req_header(conn, "origin") do
[origin] when origin == allowed_origin -> conn
[referer] when referer == allowed_origin -> conn
_ -> Plug.Conn.send_resp(conn, 403, "Forbidden") |> halt()
end
end
5. Use POST with Request Body Validation and Avoid GET for Sensitive Actions
Design endpoints so that sensitive operations require a POST with a JSON body and do not rely on query parameters. Combine this with strict parameter validation to ensure that forged GET requests cannot trigger state changes.
# In router.ex
post "/api/v1/users/email", UserController, :update_email, :api
# In controller
plug validate_json_body when action == :update_email
defp validate_json_body(conn, _) do
case conn.body_params do
%{"email" => email} when is_binary(email) and email =~ ~r/@/ -> conn
_ -> Plug.Conn.send_resp(conn, 400, "Missing or invalid email") |> halt()
end
end
By combining these measures—SameSite cookies, frame denial headers, CSRF-like origin checks, and strict parameter validation—you reduce the risk that Bearer Token–protected Phoenix endpoints can be abused via clickjacking, even when the application is embedded in hostile contexts.