Skip to content

fix(auth): use 127.0.0.1 for OAuth loopback redirect instead of localhost#10796

Open
ATKasem wants to merge 2 commits into
firebase:mainfrom
ATKasem:fix/login-loopback-127-0-0-1
Open

fix(auth): use 127.0.0.1 for OAuth loopback redirect instead of localhost#10796
ATKasem wants to merge 2 commits into
firebase:mainfrom
ATKasem:fix/login-loopback-127-0-0-1

Conversation

@ATKasem

@ATKasem ATKasem commented Jul 14, 2026

Copy link
Copy Markdown

Description

firebase login builds the Google OAuth loopback redirect as http://localhost:${port} (getCallbackUrl in src/auth.ts).

On dual-stack hosts, the browser can resolve localhost to ::1 while the OAuth redirect never reaches the CLI, and login hangs on Waiting for authentication....

This uses the 127.0.0.1 IP literal for the Google redirect URI so it matches Google's OAuth 2.0 loopback requirement (IP literal, not localhost).

The GitHub login flow stays on localhost, since GitHub validates redirect_uri against its registered OAuth callback URL.

The local callback server still binds with server.listen(port) (all interfaces), so both 127.0.0.1 (Google) and localhost/::1 (GitHub) can reach it on dual-stack hosts.

Fixes #10750.

Scenarios Tested

  • Unit tests: getCallbackUrl(port, "127.0.0.1")http://127.0.0.1:${port}, default host stays localhost, no port yields the OOB URN.
  • npm run mocha -- src/auth.spec.ts passes.

Sample Commands

firebase login

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request addresses an issue where firebase login hangs on dual-stack hosts by changing the OAuth loopback redirect from localhost to 127.0.0.1 and restricting the local server to listen on 127.0.0.1. Review feedback warns that these changes could break other authentication flows, such as GitHub OAuth, which may strictly require localhost or fail on dual-stack hosts when the server does not bind to the IPv6 loopback interface (::1). It is recommended to revert the server binding restriction to maintain compatibility while keeping the IP literal in the redirect URL.

Comment thread src/auth.ts Outdated
Comment on lines 286 to 295
export function getCallbackUrl(port?: number): string {
if (typeof port === "undefined") {
return "urn:ietf:wg:oauth:2.0:oob";
}
return `http://localhost:${port}`;
// Use the IP literal rather than "localhost" for the OAuth loopback redirect.
// Google's OAuth 2.0 loopback flow requires an IP literal, and on dual-stack
// hosts "localhost" can resolve to ::1 while the listener holds the port on
// 127.0.0.1 (or vice versa), which leaves `firebase login` hanging.
return `http://127.0.0.1:${port}`;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Changing getCallbackUrl to always return 127.0.0.1 might break the GitHub OAuth flow. GitHub's OAuth App configuration is typically configured with a strict redirect URI (often http://localhost). If the registered redirect URI in the GitHub App is http://localhost, sending 127.0.0.1 will result in a redirect_uri_mismatch error.\n\nConsider separating the callback URL generation for Google and GitHub, or ensuring that the GitHub OAuth App configuration is updated to support 127.0.0.1 before rolling this out.

Comment thread src/auth.ts Outdated
});

server.listen(port, () => {
server.listen(port, "127.0.0.1", () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Restricting the local server to listen only on 127.0.0.1 can break authentication flows that still rely on localhost (such as GitHub login, if its OAuth App configuration is strictly registered with http://localhost and cannot easily be changed to 127.0.0.1).\n\nOn dual-stack hosts, the browser may resolve localhost to ::1 (IPv6 loopback) and attempt to connect to [::1]:port. Since the server is strictly listening on 127.0.0.1, this connection will be refused, breaking the GitHub login flow.\n\nBy reverting to server.listen(port) (which binds to all interfaces, including both 127.0.0.1 and ::1), we ensure maximum compatibility for both IPv4 and IPv6 loopback connections, while still resolving the Google OAuth dual-stack issue because the Google redirect URI itself uses the 127.0.0.1 IP literal directly.

Suggested change
server.listen(port, "127.0.0.1", () => {
server.listen(port, () => {

@ATKasem ATKasem force-pushed the fix/login-loopback-127-0-0-1 branch from 943a5ca to 5bd52b6 Compare July 14, 2026 01:09
…alhost

`firebase login` builds the Google OAuth loopback redirect as
`http://localhost:${port}` and starts the callback server with
`server.listen(port)` (bound to all interfaces). On dual-stack hosts the
browser can resolve `localhost` to ::1 while the listener holds the port on
127.0.0.1 (or a locked-down hosts file remaps `localhost`), so the redirect
never reaches the CLI and login hangs on 'Waiting for authentication...'.

Use the 127.0.0.1 IP literal for both the redirect URI and the listener in the
Google flow, as expected by Google's OAuth 2.0 loopback flow. The GitHub flow
keeps `localhost`, since GitHub validates the redirect_uri host against its
registered OAuth callback URL.

Fixes firebase#10750
@ATKasem ATKasem force-pushed the fix/login-loopback-127-0-0-1 branch from 5bd52b6 to e7f9376 Compare July 16, 2026 12:30
Keep Google redirect on 127.0.0.1 and GitHub on localhost, but listen
without a host so dual-stack localhost/::1 clients can still reach the
callback (Gemini review on firebase#10796).
@ATKasem

ATKasem commented Jul 16, 2026

Copy link
Copy Markdown
Author

Addressed the Gemini review feedback:

  • Google redirect stays on 127.0.0.1 (needed for Google's loopback IP-literal requirement + the dual-stack hang).
  • GitHub redirect stays on localhost (registered callback host).
  • Callback server is back to server.listen(port) with no host binding, so localhost/::1 clients can still reach it.

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.

firebase login: use 127.0.0.1 (not localhost) for OAuth loopback + surface --no-localhost hint on hang

2 participants