Skip to content

Conversation

@jamilbk
Copy link
Member

@jamilbk jamilbk commented Dec 31, 2025

Accepts the token in either the x-authorization header or the existing token param, prioritizing the former. The X- header is used because Phoenix doesn't expose arbitrary HTTP headers in the WebSocket connect callback.

Related: #9581
Related: #11595

@vercel
Copy link

vercel bot commented Dec 31, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
firezone Ready Ready Preview, Comment Dec 31, 2025 5:38am

@jamilbk jamilbk marked this pull request as ready for review December 31, 2025 13:37
Copilot AI review requested due to automatic review settings December 31, 2025 13:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds support for passing authentication tokens via the x-authorization HTTP header (in addition to the existing token query parameter) for WebSocket connections. This change improves security by preventing tokens from being logged in query parameters, which is a common issue with many logging and monitoring tools. The X- prefix is used because Phoenix only exposes x-* and sec-websocket-* headers in WebSocket connection callbacks.

  • Introduces a new extract_token/2 function in PortalAPI.Sockets that checks for tokens in headers first (with "Bearer" prefix), then falls back to query parameters
  • Refactors client, gateway, and relay socket implementations to use the new token extraction logic
  • Adds comprehensive test coverage for token extraction from headers, including precedence tests

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
elixir/lib/portal_api/sockets.ex Adds extract_token/2 function to extract tokens from x-authorization header or token parameter
elixir/lib/portal_api/client/socket.ex Refactors connect/3 to use extract_token/2 and extracts authentication logic into do_connect/4
elixir/lib/portal_api/gateway/socket.ex Refactors connect/3 to use extract_token/2 and extracts authentication logic into do_connect/4
elixir/lib/portal_api/relay/socket.ex Refactors connect/3 to use extract_token/2 and extracts authentication logic into do_connect/4
elixir/test/portal_api/sockets_test.exs Adds comprehensive unit tests for the extract_token/2 function
elixir/test/portal_api/client/socket_test.exs Adds integration tests for header-based token authentication and precedence
elixir/test/portal_api/gateway/socket_test.exs Adds integration tests for header-based token authentication and precedence
elixir/test/portal_api/relay/socket_test.exs Adds integration tests for header-based token authentication and precedence


defp extract_token_from_header(%{x_headers: x_headers}) when is_list(x_headers) do
case List.keyfind(x_headers, "x-authorization", 0) do
{"x-authorization", "Bearer " <> token} -> {:ok, token}
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pattern matching "Bearer " <> token will accept an empty string as a valid token when the header value is exactly "Bearer " (with trailing space but no token). This could lead to accepting invalid authentication attempts. Consider adding a guard clause to ensure the token is not empty: when byte_size(token) > 0 or explicitly check for non-empty strings.

Suggested change
{"x-authorization", "Bearer " <> token} -> {:ok, token}
{"x-authorization", "Bearer " <> token} when byte_size(token) > 0 -> {:ok, token}

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +74
test "falls back to params when header value is empty" do
params = %{"token" => "fallback-token"}
connect_info = %{x_headers: [{"x-authorization", ""}]}

assert Sockets.extract_token(params, connect_info) == {:ok, "fallback-token"}
end
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test case for an edge case where the x-authorization header contains only "Bearer " (with trailing space but no token). The current implementation would accept this as a valid token with an empty string value, which should likely be rejected. Add a test to verify the expected behavior in this scenario.

Copilot uses AI. Check for mistakes.

defp extract_token_from_header(_connect_info), do: :error

defp extract_token_from_params(%{"token" => token}) when is_binary(token), do: {:ok, token}
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function accepts an empty string as a valid token when the token parameter is present but empty. While the guard clause when is_binary(token) ensures it's a string, it doesn't validate that the token is non-empty. Consider adding and byte_size(token) > 0 to the guard clause to reject empty tokens, consistent with proper token validation.

Suggested change
defp extract_token_from_params(%{"token" => token}) when is_binary(token), do: {:ok, token}
defp extract_token_from_params(%{"token" => token}) when is_binary(token) and byte_size(token) > 0,
do: {:ok, token}

Copilot uses AI. Check for mistakes.
@jamilbk jamilbk merged commit 96ca73b into main Dec 31, 2025
107 checks passed
@jamilbk jamilbk deleted the feat/authz-header branch December 31, 2025 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants