Repository navigation
[bug] bl auth login --console never completes on Chrome: local callback server omits Access-Control-Allow-Private-Network #190
Description
Summary
Update (follow-up comment below): I have since tested this on every engine. Chrome, Brave and Arc block it as described here; Safari blocks it too, as mixed content, which no server header can fix; Firefox is the only browser that completes the flow. Importantly, adding the header below is necessary but not sufficient on Chrome 152 — I proxied the callback with
Access-Control-Allow-Private-Network: truepresent and Chrome still refused to send the POST, because it is now gated by a permission. See the follow-up for the full matrix, the proxy evidence, and revised suggestions.
bl auth login --console never completes in Google Chrome. The browser page reaches "Authorize CLI Login", the click succeeds against the console backend, but the token is never delivered to the CLI: the local callback server rejects the browser's Private Network Access preflight, so the fetch is blocked and the CLI waits forever.
The fix is one response header on the local callback server.
Environment
bl --version |
1.20.0 |
| Node.js | v24.15.0 |
| OS | macOS 26.6.2 (arm64) |
| Browser | Google Chrome 152.0.7977.83 |
| Console site | international (--console-site international) |
Reproduced consistently. Not specific to one account.
Steps to reproduce
- Make Google Chrome the default browser.
- Run:
bl auth login --console --console-site international - The CLI prints
Opened the login page in your default browser...and starts listening on127.0.0.1:<port>. - Chrome opens
https://modelstudio.console.alibabacloud.com/console-login?notice=127.0.0.1:<port>&state=<state>. - Click Authorize CLI Login.
Expected
The page posts the token to http://127.0.0.1:<port>/?state=<state>, the CLI stores access_token in the active profile of ~/.bailian/config.json, and the command exits successfully.
Actual
The button shows a loading state forever. The CLI keeps waiting and writes nothing: ~/.bailian/config.json is unchanged and has no access_token, so every bl usage ... subcommand keeps failing with:
{"error":{"code":3,"message":"No console access token found.","hint":"Run `bl auth login --console`."}}Chrome DevTools console on the authorize page:
Access to fetch at 'http://127.0.0.1:55259/?state=<state>' from origin
'https://modelstudio.console.alibabacloud.com' has been blocked by CORS policy:
Permission was denied for this request to access the `loopback` address space.
POST http://127.0.0.1:55259/?state=<state> net::ERR_FAILED
authorize failed: TypeError: Failed to fetch
Note that the console API calls on that page succeed — the account is authenticated and the token is minted. Only the hand-off to the CLI fails.
Root cause
Chrome enforces Private Network Access: when a public HTTPS page requests a loopback address, Chrome sends a CORS preflight carrying Access-Control-Request-Private-Network: true, and the local server must answer with Access-Control-Allow-Private-Network: true. Otherwise the request is blocked before it is sent.
The CLI's local callback server answers the preflight without that header:
$ curl -i -X OPTIONS "http://127.0.0.1:55259/" \
-H "Origin: https://modelstudio.console.alibabacloud.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Private-Network: true"
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, OPTIONS
Access-Control-Allow-Headers: Content-Type
Date: Fri, 04 Sep 2026 14:56:56 GMT
Connection: keep-alive
Keep-Alive: timeout=5Access-Control-Allow-Private-Network is absent, so Chrome denies the loopback access and the POST never leaves the page. The server itself is healthy — a direct request from outside the browser reaches it and is answered (HTTP 400 for a bare GET /, as expected).
Suggested fix
Echo the header on the preflight response of the login callback server, only when the browser asks for it:
if (request.method === 'OPTIONS') {
const headers = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
if (request.headers['access-control-request-private-network'] === 'true') {
headers['Access-Control-Allow-Private-Network'] = 'true';
}
response.writeHead(204, headers);
response.end();
return;
}This is backward compatible: browsers that do not send the request header are unaffected.
Two things worth considering alongside it:
- Fail loudly instead of hanging. Today the command waits forever with no output. A timeout that prints "the browser could not deliver the token" would turn a silent hang into a diagnosable error.
- Offer a manual fallback. Printing the callback URL, or accepting a pasted token, gives users a way through when the browser blocks the hand-off for any reason.
Workaround for other users
Until this is fixed, set a browser that does not enforce Private Network Access (Safari or Firefox) as the system default before running bl auth login --console, since the CLI opens the default browser.
Alternatively, capture the token in Chrome DevTools (the authorize request returns {"access_token": "...", "token_type": "Bearer", "expires_in": 86400}) and write it as access_token into the active profile of ~/.bailian/config.json.
Impact
Chrome is the default browser for a large share of users, and this blocks the only console-login path. On a Token Plan Personal account there is no alternative: bl auth login --open-api cannot substitute for it, because minting the CLI token via GenerateCLIAccessToken returns NoPermission for a personal account (verified with AliyunTokenPlanReadOnlyAccess, AliyunTokenPlanFullAccess and AliyunBSSReadOnlyAccess all attached). So for those users, every bl usage token-plan is unreachable on Chrome.
Activity
ricardok commentedon Sep 4, 2026
Follow-up: tested across all three engines — and the missing header is necessary but not sufficient on current Chrome
After filing this I tested the same login on every browser available on the machine. The short version: Chromium and WebKit both block the hand-off, for two different reasons, and only Firefox completes it. That makes the problem broader than the header I proposed above.
Results
blocked by CORS policy: Permission was denied for this request to access the loopback address space[blocked] The page ... requested insecure content from http://127.0.0.1:<port>/... This content was blockedConfig saved to ~/.bailian/config.jsonThe header alone does not unblock Chrome 152
To isolate the cause I put a local proxy in front of the CLI's callback server. It answers the preflight with everything the CLI sends plus
Access-Control-Allow-Private-Network: true, then forwards the request to the CLI:I then pointed the login page at that proxy (same flow,
notice=127.0.0.1:51999). The proxy log shows Chrome did send the preflight:…and the POST never followed. Chrome reported the same error as before. So on Chrome 152 this is gated by a permission, not merely by a response header — note the wording, "Permission was denied". Adding the header is still correct and still worth doing (it is required by the spec and unblocks older Chrome versions), but on current Chrome it is not enough on its own.
Safari is unaffected by any of this: it rejects the request earlier, as mixed content, because WebKit does not treat
http://127.0.0.1as a potentially trustworthy origin the way Chromium does. No response header can fix that from the server side.What actually worked
Installing Firefox and running the unmodified flow through it:
bl usage token-plan --output jsonthen returned the quota normally.One detail worth knowing for the docs: on the first run Firefox showed a permission dialog asking to allow opening another application, and after allowing it the login completed. Users should be told to expect and accept that prompt, otherwise it looks like another failure.
Since the CLI opens the system default browser, users whose default is Chrome cannot reach Firefox without changing that setting. In our case we worked around it by putting a shim named
openahead of the CLI onPATH, which forwards the URL to Firefox — that is a workaround no ordinary user will find.Suggested changes, revised
Access-Control-Allow-Private-Network: trueon the preflight. Still correct, still needed — just not sufficient on Chrome 152.http://localhost. Both major engines are closing this path: Chromium behind a permission, WebKit as mixed content. A device-code style flow — the console displays a short code, the CLI polls the backend for the result — removes the browser from the delivery path entirely and works in every browser.--no-browser/ manual mode in the meantime: print the URL instead of opening it, and accept the token pasted back into the terminal. This costs little and gives everyone an escape hatch today.Happy to test a patch against this environment if that helps.
gujieye commentedon Oct 22, 2026
Thanks for the detailed browser matrix and proxy experiment. Your follow-up is important: the missing PNA header and browser-enforced restrictions are separate problems. Adding the header alone cannot bypass Chromium's Local Network Access permission or Safari's mixed-content block.
The CLI-side patch addresses the following:
Access-Control-Allow-Private-Network: truewhen requested by a preflight from the expected console origin. CORS is restricted to that origin, including error responses; callback state validation remains required.--no-browserso you can choose Firefox without changing the system default browser.5), instead of treating callback-server shutdown as successful login. Browser-launch failure and client disconnection during credential saving are also covered.--token-stdinas a fallback that does not use a browser or local callback server. The CLI validates the supplied token against the selected console gateway before saving it; validation failure leaves existing credentials unchanged.With this patch, manual browser selection is:
Keep the command running and open the complete printed URL in Firefox on the same machine. If Firefox shows the application-opening prompt you reported, allow it to continue.
--no-browserchanges how the page is opened; it does not itself bypass browser security restrictions.Alternatively, import an existing valid Console access token (POSIX shell example, international site / Singapore):
bl auth login --console --token-stdin \ --console-site international \ --console-region ap-southeast-1 < /path/to/console-token.txtUse a private file containing only the raw token, not JSON, a
Bearerprefix, or a model API key. Interactive terminal input is rejected to avoid echoing secrets, and input is limited to 64 KiB. Do not put the token in command arguments or post it here; remove the temporary credential file after use. This path does not create a model API key.The targeted regression suite passes 68 tests, with 4 live-credential tests skipped. These results cover the CLI behavior, not successful login in your exact browser/account environment.
A device-code/polling flow would require coordinated console/backend changes and is outside this CLI-side patch. We are keeping this issue open pending end-to-end verification in the reported environment. When retesting, please check that login completes and
bl usage token-plan --output jsonworks afterward, and share the browser version, permission-prompt behavior, and any sanitized error output.