How Topaz enables az login without root: MSAL, port 443, and a built-in CONNECT proxy | Topaz
Skip to main content<br>One of the fundamental rules of Topaz is that it must not require sudo or admin rights. There is nothing more frustrating than having to request elevated permissions on your machine just to run a dev tool.
For most Azure CLI operations this is not a problem. You point the CLI at https://topaz.local.dev:8899, it talks to port 8899, done. ROPC login is the exception - az login --username --password triggers a user-realm discovery pre-flight inside MSAL that always targets port 443, regardless of what port you configured in the authority URL. On a non-Docker Topaz install, nothing is listening on port 443, because binding that port requires root. The result is a connection timeout that surfaces as an opaque account-not-found error with no indication that port 443 is involved at all.
This post explains the MSAL assumption behind that behavior, why the straightforward fixes do not work without elevated permissions, and how a built-in HTTP CONNECT proxy on port 44380 closes the gap cleanly.
Coming in v1.6<br>ROPC authentication via the built-in CONNECT proxy is coming in Topaz v1.6. For non-container installs, you will set HTTPS_PROXY=http://127.0.0.1:44380 before running:az login --username topazadmin@topaz.local.dev --password admin
Docker installs do not need the proxy: port 443 is bound directly.brew tap thecloudtheory/topaz && brew install topaz # macOS
curl -fsSL https://raw.githubusercontent.com/TheCloudTheory/Topaz/main/install/get-topaz.sh | bash # Linux
Entra ID emulation docs → · Star on GitHub →
Background: why ROPC matters locally
Topaz emulates Microsoft Entra ID as a first-class capability. That includes the token endpoint, the OIDC discovery document, and a built-in superadmin user (topazadmin@topaz.local.dev). The usual local authentication path is to configure the Azure CLI environment with a custom authority URL pointing at https://topaz.local.dev:8899, then call az login --username --password and let MSAL handle the rest. You may still use other flows or just leverage service principal credentials, but for many common scenarios, this is the easiest path.
This works correctly in Docker, where the container binds port 443 directly. On a non-container install (Homebrew on macOS, the get-topaz.sh script on Linux), port 443 requires root. Topaz runs as a regular user on port 8899. For everything except az login, that distinction does not matter: you tell the CLI the authority is https://topaz.local.dev:8899, the CLI talks to port 8899, done. For az login, it matters quite a lot, and the failure message does not tell you why (unless you're quite proficient with reading Python errors).
The problem in practice
Take a standard non-Docker install with the Topaz host running on port 8899. The cloud environment is configured as:
az cloud register \
--name Topaz \
--endpoint-resource-manager "https://topaz.local.dev:8899" \
--endpoint-active-directory "https://topaz.local.dev:8899/" \
--endpoint-active-directory-resource-id "https://topaz.local.dev:8899"
az cloud set --name Topaz
az login --username topazadmin@topaz.local.dev --password admin
The error:
HTTPSConnectionPool(host='topaz.local.dev', port=443): Max retries exceeded with url:
/common/userrealm/topazadmin@topaz.local.dev?api-version=1.0
(Caused by NewConnectionError("HTTPSConnection(host='topaz.local.dev', port=443):
Failed to establish a new connection: [Errno 111] Connection refused"))
Port 443 is not listening, so the connection is immediately refused. MSAL was not talking to port 8899 at all.
My first assumption was a DNS issue. topaz.local.dev resolves to 127.0.0.1 via Topaz's dnsmasq configuration, which is straightforward. I confirmed the host entry was in place and that a direct curl https://topaz.local.dev:8899/.well-known/openid-configuration returned the expected OIDC document.
It was not DNS.
What MSAL does before requesting a token
Before MSAL attempts the actual ROPC token exchange (grant_type=password to the /token endpoint), it makes a preliminary call. The call is called the user-realm discovery pre-flight:
GET https://topaz.local.dev/common/userrealm/topazadmin%40topaz.local.dev?api-version=1.0
The purpose is to determine whether the account uses federated authentication or a managed (password) flow. If the pre-flight fails, MSAL never proceeds to the token request. It returns an error that looks like an account-not-found or a connectivity problem, depending on whether the timeout fires first.
The key detail is in the URL. Notice the hostname: topaz.local.dev, with no port. MSAL builds this URL from the authority it was given, but it strips the port number. The reasoning, as far as I can tell from reading the MSAL source, is that user-realm discovery is supposed to hit login.microsoftonline.com at the default HTTPS port. For real Azure, this is reasonable: the endpoint is always on...